Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wish: 'hard assignment' operator #15464

Closed
p5pRT opened this issue Jul 20, 2016 · 7 comments
Closed

Wish: 'hard assignment' operator #15464

p5pRT opened this issue Jul 20, 2016 · 7 comments

Comments

@p5pRT
Copy link

p5pRT commented Jul 20, 2016

Migrated from rt.perl.org#128678 (status was 'rejected')

Searchable as RT128678$

@p5pRT
Copy link
Author

p5pRT commented Jul 20, 2016

From @epa

Created by @epa

I don't know about you, but I find my code full of checks like this​:

  foreach my $i (@​items) {
  die "seen $_ twice" if defined $score{$i};
  $score{$i} = get_score($i);
  }

(Here 'exists' could be more appropriate than 'defined' if the score
itself can be undef, but for the scope of this discussion I am
considering definedness.)

Or perhaps this​:

  die if defined $x;
  $x = $y;

Or perhaps they can be written in a more 'topical' way as

  foreach my $i (@​items) {
  for ($score{$i}) {
  die if defined;
  $_ = get_score($i);
  }
  }

That might be a bit faster, only doing the hash lookup once, but it is
still four lines of code when the key idea is the assignment. The
'die' is intended to be an impossible condition, checked for to catch
bugs early, and to document the programmer's intention.

The wish is to have a 'hard assignment' operator which will set
something that's currently undefined, but die if it's already defined.
Suppose := is used (I am not suggesting that is the best syntax, it
is just for the sake of example). Then

  foreach my $i (@​items) {
  $score{$i} := get_score($i);
  }

  $x := $y;

The addition of the // operator to the language simplified a lot of
code. Things which needed two statements, or some careful dance with
'for', can now be written in a single statement which does the desired
error check with only a couple of extra keystrokes. I believe that a
hard-assignment operator would be a similarly useful 'Huffman coding'.

Perl Info

Flags:
    category=core
    severity=wishlist

Site configuration information for perl 5.22.2:

Configured by Red Hat, Inc. at Thu Jul  7 13:04:52 UTC 2016.

Summary of my perl5 (revision 5 version 22 subversion 2) configuration:
   
  Platform:
    osname=linux, osvers=4.4.9-300.fc23.x86_64, archname=x86_64-linux-thread-multi
    uname='linux buildvm-07.phx2.fedoraproject.org 4.4.9-300.fc23.x86_64 #1 smp wed may 4 23:56:27 utc 2016 x86_64 x86_64 x86_64 gnulinux '
    config_args='-des -Doptimize=none -Dccflags=-O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches  -m64 -mtune=generic -Dldflags=-Wl,-z,relro  -Dccdlflags=-Wl,--enable-new-dtags -Wl,-z,relro  -Dlddlflags=-shared -Wl,-z,relro  -Dshrpdir=/usr/lib64 -DDEBUGGING=-g -Dversion=5.22.2 -Dmyhostname=localhost -Dperladmin=root@localhost -Dcc=gcc -Dcf_by=Red Hat, Inc. -Dprefix=/usr -Dvendorprefix=/usr -Dsiteprefix=/usr/local -Dsitelib=/usr/local/share/perl5 -Dsitearch=/usr/local/lib64/perl5 -Dprivlib=/usr/share/perl5 -Dvendorlib=/usr/share/perl5/vendor_perl -Darchlib=/usr/lib64/perl5 -Dvendorarch=/usr/lib64/perl5/vendor_perl -Darchname=x86_64-linux-thread-multi -Dlibpth=/usr/local/lib64 /lib64 /usr/lib64 -Duseshrplib -Dusethreads -Duseithreads -Dusedtrace=/usr/bin/dtrace -Duselargefiles -Dd_semctl_semun -Di_db -Ui_ndbm -Di_gdbm -Di_shadow -Di_syslog -Dman3ext=3pm -Duseperlio -Dinstallusrbinperl=n -Ubincompat5005 -Uversiononly -Dpager=/usr/bin/less -isr -Dd_gethostent_r_proto -Ud_endhostent_r_proto -Ud_sethostent_r_proto -Ud_endprotoent_r_proto -Ud_setprotoent_r_proto -Ud_endservent_r_proto -Ud_setservent_r_proto -Dscriptdir=/usr/bin -Dusesitecustomize'
    hint=recommended, useposix=true, d_sigaction=define
    useithreads=define, usemultiplicity=define
    use64bitint=define, use64bitall=define, uselongdouble=undef
    usemymalloc=n, bincompat5005=undef
  Compiler:
    cc='gcc', ccflags ='-D_REENTRANT -D_GNU_SOURCE -O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fwrapv -fno-strict-aliasing -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64',
    optimize='  -g',
    cppflags='-D_REENTRANT -D_GNU_SOURCE -O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fwrapv -fno-strict-aliasing -I/usr/local/include'
    ccversion='', gccversion='5.3.1 20160406 (Red Hat 5.3.1-6)', gccosandvers=''
    intsize=4, longsize=8, ptrsize=8, doublesize=8, byteorder=12345678, doublekind=3
    d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=16, longdblkind=3
    ivtype='long', ivsize=8, nvtype='double', nvsize=8, Off_t='off_t', lseeksize=8
    alignbytes=8, prototype=define
  Linker and Libraries:
    ld='gcc', ldflags ='-Wl,-z,relro  -fstack-protector-strong -L/usr/local/lib'
    libpth=/usr/local/lib64 /lib64 /usr/lib64 /usr/local/lib /usr/lib /lib/../lib64 /usr/lib/../lib64 /lib
    libs=-lpthread -lresolv -lnsl -lgdbm -ldb -ldl -lm -lcrypt -lutil -lc -lgdbm_compat
    perllibs=-lpthread -lresolv -lnsl -ldl -lm -lcrypt -lutil -lc
    libc=libc-2.22.so, so=so, useshrplib=true, libperl=libperl.so
    gnulibc_version='2.22'
  Dynamic Linking:
    dlsrc=dl_dlopen.xs, dlext=so, d_dlsymun=undef, ccdlflags='-Wl,--enable-new-dtags -Wl,-z,relro '
    cccdlflags='-fPIC', lddlflags='-shared -Wl,-z,relro  -L/usr/local/lib -fstack-protector-strong'

Locally applied patches:
    Fedora Patch1: Removes date check, Fedora/RHEL specific
    Fedora Patch3: support for libdir64
    Fedora Patch4: use libresolv instead of libbind
    Fedora Patch5: USE_MM_LD_RUN_PATH
    Fedora Patch6: Skip hostname tests, due to builders not being network capable
    Fedora Patch7: Dont run one io test due to random builder failures
    Fedora Patch15: Define SONAME for libperl.so
    Fedora Patch16: Install libperl.so to -Dshrpdir value
    Fedora Patch22: Document Math::BigInt::CalcEmu requires Math::BigInt (CPAN RT#85015)
    Fedora Patch26: Make *DBM_File desctructors thread-safe (RT#61912)
    Fedora Patch27: Make PadlistNAMES() lvalue again (CPAN RT#101063)
    Fedora Patch28: Make magic vtable writable as a work-around for Coro (CPAN RT#101063)
    Fedora Patch29: Fix duplicating PerlIO::encoding when spawning threads (RT#31923)
    Fedora Patch30: Do not let XSLoader load relative paths (RT#115808)
    Fedora Patch200: Link XS modules to libperl.so with EU::CBuilder on Linux
    Fedora Patch201: Link XS modules to libperl.so with EU::MM on Linux


@INC for perl 5.22.2:
    /home/eda/lib64/perl5/
    /usr/local/lib64/perl5
    /usr/local/share/perl5
    /usr/lib64/perl5/vendor_perl
    /usr/share/perl5/vendor_perl
    /usr/lib64/perl5
    /usr/share/perl5
    .


Environment for perl 5.22.2:
    HOME=/home/eda
    LANG=en_GB.UTF-8
    LANGUAGE (unset)
    LC_COLLATE=C
    LC_CTYPE=en_GB.UTF-8
    LC_MESSAGES=en_GB.UTF-8
    LC_MONETARY=en_GB.UTF-8
    LC_NUMERIC=en_GB.UTF-8
    LC_TIME=en_GB.UTF-8
    LD_LIBRARY_PATH (unset)
    LOGDIR (unset)
    PATH=/home/eda/bin:/home/eda/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/usr/sbin:/home/eda/.local/bin:/home/eda/bin:/sbin:/usr/sbin
    PERL5LIB=/home/eda/lib64/perl5/
    PERL_BADLANG (unset)
    SHELL=/bin/bash

@p5pRT
Copy link
Author

p5pRT commented Jul 20, 2016

From @iabyn

On Wed, Jul 20, 2016 at 06​:46​:29AM -0700, Ed Avis wrote​:

foreach my $i (@​items) {
$score{$i} := get_score($i);
}

$x := $y;

The addition of the // operator to the language simplified a lot of
code. Things which needed two statements, or some careful dance with
'for', can now be written in a single statement which does the desired
error check with only a couple of extra keystrokes. I believe that a
hard-assignment operator would be a similarly useful 'Huffman coding'.

For the cases where it is an error to assign to a non-undef lvalue,
I would imagine that normally you'd want a specific error message,
e.g.

  for (@​flags) {
  die "duplicate flag $_ found in $config_file" if $config{$_};
  $config{$_} = 1;
  }

So I would expect a hard-assignment operator which just prints a generic
error message to have a relatively small application.

--
Little fly, thy summer's play my thoughtless hand
has terminated with extreme prejudice.
  (with apologies to William Blake)

@p5pRT
Copy link
Author

p5pRT commented Jul 20, 2016

The RT System itself - Status changed from 'new' to 'open'

@p5pRT
Copy link
Author

p5pRT commented Jul 20, 2016

From @epa

Dave Mitchell <davem <at> iabyn.com> writes​:

For the cases where it is an error to assign to a non-undef lvalue,
I would imagine that normally you'd want a specific error message,

It depends. If validating user-supplied data such as command line arguments,
yes. But much of the time you are dealing with internal data which you expect
to be unique; the 'die' is expected never to be hit. (If it is, there is some
logic error in the program which the user wouldn't be able to diagnose.)

It's similar for checks done with the // operator. For user-supplied data
you want to provide a helpful message​:

  my $separator = $opt{separator} // die 'the separator option is required';

But for internal data, where as the programmer you're pretty sure it will
always be set (and want to find out ASAP if your assumption is wrong), it's
easier just to 'die' without much fanfare​:

  my $page = $h{page} // die;

Of course, you can start off with the plain 'die;' style for quick hacking
and later improve the error messages. That is a useful feature of perl, that
it lets you die quickly with a default message if you want. The hard-
assignment error message could report the name of the variable, its existing
value and the new value that was to be assigned. That default message
would be good enough in a lot of cases.

So while I would still keep some manual checks in my code for cases that
are catching user mistakes, for the large number of 'not already defined'
checks whose purpose is to catch a programmer mistake, a hard-assignment
operator would cut out a lot of boilerplate.

--
Ed Avis <eda@​waniasset.com>

@p5pRT
Copy link
Author

p5pRT commented Jul 20, 2016

From @kentfredric

On 21 July 2016 at 01​:46, Ed Avis <perlbug-followup@​perl.org> wrote​:

The wish is to have a 'hard assignment' operator which will set
something that's currently undefined, but die if it's already defined.
Suppose := is used (I am not suggesting that is the best syntax, it
is just for the sake of example). Then

This feature is too niche for a top level language syntax.

I'd probably be looking more into a utility for `Hash​::Util` that
marks an entire hash, or specific keys in a hash as "write-once",
similar to the existing lock_value function.

  lock_defined( \%score );
  foreach my $i (@​items) {
  $score{$i} = get_score($i);
  }

Or at variable level, maybe you want a CPAN Module that adds magic to
variables that makes them write-once ( Akin to the readonly interfaces
)

use Imaginary​::WriteOnce;
write_once( $x );
$x = 1 ; # ok
$x = 1 ; # attempt to set defined write-once value "$x" to "1"

But generally the idea of "explode on write" seems very bad.

I can understand "explode on missing", ie​:

$x := $y{path};

not defined $y ? die "Expected path property of $y to be defined, got undef" : $x = $y;

But you wanted the opposite to that, and that disturbs me.

$x := $y{path}

not defined $x ? die "Variable $x already defined" : $x = $y;

Because I frequently find use for the former of those two examples,
but the latter is rare as hell.

--
Kent

KENTNL - https://metacpan.org/author/KENTNL

@p5pRT
Copy link
Author

p5pRT commented Jul 20, 2016

From @ap

* Ed Avis <perlbug-followup@​perl.org> [2016-07-20 15​:48]​:

I don't know about you, but I find my code full of checks like this​:

foreach my $i \(@&#8203;items\) \{
    die "seen $\_ twice" if defined $score\{$i\};
    $score\{$i\} = get\_score\($i\);
\}

(Here 'exists' could be more appropriate than 'defined' if the score
itself can be undef, but for the scope of this discussion I am
considering definedness.)

Or perhaps they can be written in a more 'topical' way as

foreach my $i \(@&#8203;items\) \{
    for \($score\{$i\}\) \{
        die if defined;
        $\_ = get\_score\($i\);
    \}
\}

You don’t need a new language feature.

  sub ensure_undef ($) : lvalue { defined $_[0] ? croak 'Unexpected defined value' : $_[0] }

  foreach my $i (@​items) {
  ensure_undef $score{$i} = get_score($i);
  }

Regards,
--
Aristotle Pagaltzis // <http​://plasmasturm.org/>

@p5pRT p5pRT closed this as completed Aug 10, 2016
@p5pRT
Copy link
Author

p5pRT commented Aug 10, 2016

@iabyn - Status changed from 'open' to 'rejected'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant