3

Are there any good uses for Perl's fat commas in series?

func_hash_as_array_arg( **a=>b=>1** )

I just got bitten by a bug caused by two fat commas / fat arrows in series:

$ bash $> perl -e 'use strict; use warnings; my @v = ( a=> b => 1 )'
✓ 

actually in a function; actually in a constructor for an object (blessed hash), so I was thinking {} when it was new( a=>b=>1).

$ bash $>  perl -e '
    use strict; use warnings; 
    sub kwargs_func{ print "inside\n"; my %kw = $_[0] ;}; 
    kwargs_func( a=> b => 1 )
'
inside
Odd number of elements in hash assignment at -e line ##.
✓ 

Obviously I found the bug fairly quickly - but I would prefer to have had a compile-time error or warning rather than a run-time error.

Are there any good uses for fat commas in series?

I am surprised that there was not a warning for this.


Here's a contrived example of a semi-legitimate use. One that I can imagine encountering in real life:

I do a lot of graph code.

Imagine entering a constant graph like K3, K4, or K3,3 (I will assume that all arcs are bidirectional)

One might enter such graphs as pairs, like

K3: (a<=>b, a<=>b, b<=>c). 

But it might be nice to enter it as

K3: (a<=>b<=>c<=>a). 

Less repetition, as one gets to bigger graphs.

E.g. K4 written as pairs is

K4: ( a<=>b, a<=>c, a<=>d, b<=>c, b<=>d )

whereas using these "chains" K4 is:

K4: (a<=>b<=>c<=>d<=>a<=>c,b<=>d)

I have written what we now call DSL (Domain Specific Languages) that accept such "chain" notations. Note: using <=> above, deliberately non-Perl friendly syntax.

Of course, in Perl one would have to indicate the end of such a chain, probably by undef:

K4: (a=>b=>c=>d=>a=>c=>undef,b=>d=>undef)

although one might elide the last undef.

I am too lazy to type in K3,3, so let me enter K3,2:

DSL pairs K3,2: (a<=>x, a<=>y, b<=>x, b<=>y, c<=>x, c<=>y )

DSL chains: K3,2:  (y<=>a<=>x<=>b<=>y<=>c<=>x)

Perl pairs K3,2: (a=>x, a=>y, b=>x, b=>y, c=>x, c=>y )

Perl chains: K3,2:  (y=>a=>x=>b=>y=>c=>x=>undef)

I like functions with keyword arguments. In Perl there are two main ways to do this:

func_hash_as_array_arg( kwarg1=>kwval1, kwarg2=>kwval2 )
func_hashref_as_scalar_arg( { kwarg1=>kwval1, kwarg2=>kwval2 } )

which can be mixed with positional in a reasonably nice way

func( posarg1, posarg2, kwarg1=>kwval1, kwarg2=>kwval2 )
func( posarg1, posarg2, { kwarg1=>kwval1, kwarg2=>kwval2 } )

and also in less nice ways

func( { kwarg1=>kwval1, kwarg2=>kwval2 }, varargs1, vargags2, ... )

Although I prefer f(k1=>v1) to f({k1=>v1}) - less clutter - the fact that the hashref "keyword argument group" gives more compile-time checking is interesting. I may flip.

Of course, the real problem is that Perl needs a proper syntax for keyword arguments.

Perl6 does it better.


For grins, some related code examples with 2 fat commas in series.

$ bash $>  perl -e 'use strict; use warnings; my %v = ( a=> b => 1 )'
Odd number of elements in hash assignment at -e line 1.
✓

$ bash $>  perl -e 'use strict; use warnings; my $e = { a=> b => 1 }'
Odd number of elements in anonymous hash at -e line 1.
✓ 

$ bash $>  perl -e 'use strict; use warnings; my $e = [ a=> b => 1 ]'
✓ 

$ bash $>  perl -e '
    use strict; use warnings; 
    sub kwargs_func{ print "inside\n"; my %kw = $_[0] ;}; 
    kwargs_func( a=> b => 1 )
'
inside
Odd number of elements in hash assignment at -e line ##.
✓ 

$ bash $>  perl -e '
   use strict; use warnings; 
   sub kwargs_func{ print "inside\n"; my %kw = %{$_[0]} ;}; 
   kwargs_func( {a=> b => 1} )
'
Odd number of elements in anonymous hash at -e line ##.
inside
✓ 
brian d foy
  • 129,424
  • 31
  • 207
  • 592
Krazy Glew
  • 7,210
  • 2
  • 49
  • 62
  • Well, there you go then, that is an interesting use. Note though, the example code in the comment is incorrect -- the last char must be quoted. – zdim Sep 09 '16 at 19:17
  • I can see that `(a=>b=>c=>'a')` may look nice for a graph (except for that last pesky quote), but programming wise, it may be confusing -- it indicates some association between elements, while it can only be a plain list. If it's a hash then it conveys wrong things, since `b` and `c` in a hash aren't related. – zdim Sep 09 '16 at 19:37
  • Typically in a DSL implemented in Perl it might look like Graph->new_chains(a=>b=>c=>undef). Equivalent to Graph->new_chain( qw(a b c), undef). Possibly created an actual hashref multi map {a=>qw(b c d), b=>qw(a c), c=>(a b) }. – Krazy Glew Sep 10 '16 at 18:57
  • OK -- but then why that `a=>b=>c=>undef` in the interface? I see what it is by names. So what is its purpose? It does make me to think about implementation, where you shouldn't push me as a user -- and it confuses me at that, is it a hash or a list? As for the other thing, in (the current) Perl you just get one long plain list of all those chars, as they come, which are then paired for the hash. – zdim Sep 12 '16 at 06:55

5 Answers5

4

For those that don't know, the arrow functions as a comma that auto-quotes barewords on the left.

I can't think of a time where I've seen a chain of fat commas work well in regular code. Generally I would opt for a quote operator.

my @array = qw(Minimal syntax for all these words and easier than lots of arrows);
mp3
  • 154
  • 1
  • 6
1

A fat comma's main benefit is that it visually associates its two arguments, and we also get quotes for free. This is naturally helpful with hashes, while it does have occasional other uses.

However, I don't see much purpose to chaining them. Those => convey association between elements and in my opinion that is close to wrong for a plain list. If it is used to implement such behavior that is something else -- the list itself has no such property and using syntax that hints at it may be misleading (see comment at the end). Even the quoting convenience comes with a glitch

my @a_list = (a => b => 'c');  #  same as qw(a b c)

The a and b are treated as quoted since they are at the left-hand side of =>, but c isn't.

As for what and how should be reported as error, a chain of fat commas are just (fancy) commas and one is allowed to chain them, forming a list. If you happen to have an even number of them you may as well assign to a hash, it being a list with even number of elements

my %h = ('one', 1, 'two', 2);    # same as:  my %h = (one => 1, two => 2);
my %g = (one => 1 => two => 2);  # same, but misleading

I find this misleading since hash elements are associated pair-wise, not in a chained sense.

With odd number of elements we get a runtime warning, not a compilation error. This happens at assignment since up to that point there is no reason to flag an odd number of =>, it being a list.

Altogether, to me this is awkward and confusing. I don't see any benefits to it either.

I'd say -- If it's about quoting, use qw. If it's for a hash, use it normally.


If it is used to implement associative behavior, I still wouldn't try to indicate that in the list syntax. An interesting purpose came up in comments, related to graphs. Consider

create_graph(a => b => c => 'a');  # hash? list? why are these chained?

While the user shouldn't care about implementation, this interface does make me wonder what those => suggest. I should know by class/method names that it is a graph, so then why the chain, what does that mean?

zdim
  • 64,580
  • 5
  • 52
  • 81
  • Tell me something I don't know... Of course my use of (a=>b=>1) was bad - it was a bug, a coding error, perhaps caused by holding the paste key down too long and pasting twice. I agree that (a=>b=>1) seems to have no benefit - but that's what I am asking about: could we have (a=>b=>1), recognized by the Perl language parser as an error, at compile time? Or at least at the point where the bug occurs, rather than potentially much later at the point where the array is assigned to a hash. If something is "awkward, confusing, and brings no benefits", then it should be detected early. – Krazy Glew Sep 09 '16 at 12:02
  • what I ask is: "I see no benefit to (a=>b=>1), but perhaps there is semi-legitimate code that does this for a real reason. If so, what is a semi-legitimate reason to use => in series? Please show me..." After all, there are many Perl tricks - perhaps this is used by a Perl trick that I do not know. This sort of question gets answered by exhaustion: if nobody shows me any such code, after a few months I conclude there is no legitimate reason. Better, one might study this by saying "I analyzed a big corpus of Perl code, eg CPAN, and never saw (a=>b=>1). If a trick, not common." – Krazy Glew Sep 09 '16 at 12:10
  • A compile time check would catch (a=>b=>1) and also ($a=>$b=>$c). It might not catch likely errors such as (@a=>@b=>@c), but catching some early is not a bad thing. – Krazy Glew Sep 09 '16 at 12:15
  • Finally, @zdim, I found you saying "please don't do that [a=>b=>1]" curiously amusing: wouldn't it be great if we could ask nicely and have programmers never write bad code - at least not obviously bad code, no typos, misspellings, etc.? Then we would not need pesky things like "use strict" or "use warnings". :-) – Krazy Glew Sep 09 '16 at 12:21
  • @KrazyGlew I didn't mean to critize your question (or you), I meant to answer it. Is there some chunk of code that uses it "legitimately"? I don't know, and I think that we don't need to know. What I say is that there is no reason to use that and, further, that it is not good practice. That answers the question (in my opinion). Are you realy asking whether there is a piece of ocde with it lying around somewhere? Why? You know it's not good and even if there is some example of reasonable use it won't make me start doing that. – zdim Sep 09 '16 at 18:52
  • @KrazyGlew I don't see anything "_amusing_" about matters of coding practices. Most languages give you plenty of rope, and Perl has whole spools -- how to best use language features is often far from obvious. The chaning you ask about is _not an error_. The warning comes about _when you assign_ an (odd-length) array to hash, and again is _not an error_ (but a warning). Fat commas are commas (with flair) and you are allowed to use them that way. Do you want to (use a feature)? That is far more subtle and nuanced (in this case I say "better not"). – zdim Sep 09 '16 at 19:32
  • sorry, I must have been grumpy when I wrote those comments at 5am, and I did come down on you rather hard. But you did miss the point of my question, which was not "what does this do?" or "why did I get this bug?", or even "should I use this code?", but rather "thus seems like a bug-prone language feature, can we improve it?" - possibly by adding an optional warning about it. – Krazy Glew Sep 10 '16 at 18:36
  • yes, I really was asking "is there a piece of code with this lying around somewhere?" First, it is possible that there are good reasons to do a=>b=>1 that you and I might not know about (I came up with one, but not really that good). Second, as an occasional language designer and full-time computer architect, I always have to consider existing code. Eg if I added a warning about this to the Perl parser, I would not want to break existing code. But if guaranteed no existing code, I might make it an error instead if a warning. – Krazy Glew Sep 10 '16 at 18:42
  • for example, we might change the definition of => so that it is not a comma, but instead an operator that produces a duple, a key value pair that is coercible to an array. And we might further restrict => to not accept such a key value pair as its left hand side. This is one way that languages evolve. (Of course, for Perl it is moot: Perl6 is the way forward, although overall the trend seems to be to languages that do not have such large spools of hangman's rope. – Krazy Glew Sep 10 '16 at 18:48
  • Btw @zdim, you are right about: the converting odd sized array to a hash (or ditto for refs) is a runtime error, not a compile time error. Although I would be surprised if "%h = (1, 2, 3)" is not reported at compile time - seems to me like a missed opportunity - but I am on my phone and can't test right now. But I think an a=>b=>1 check could/should/would always be a compile-time check: I can't think of any time that it could not be checked at compile time but could be checked at run time. – Krazy Glew Sep 10 '16 at 19:05
  • @KrazyGlew This does change things of course. Please note that it didn't come through in the question. Still -- while I don't know of a possible good use, I do think that there isn't, by direct analysis. For a list it's misleading, for a hash it's worse. As for errors, chained `=>` are just a list, so you can't flag it. _Assigning_ odd number to a hash gets a warning. Here's a use case to justify it. I may be massively creating and assigning pairs to a hash and don't care if the some stay short (they'll be `undef`). I'll even turn off the warning in that section of code. This is reasonable. – zdim Sep 12 '16 at 06:42
  • @KrazyGlew I updated my post to (somewhat) reflect new info in your comments. I may do more editing -- there is a lot in your comments and I need to reflect. I still do believe that it answers the question, as explained in the previous comment and (hopefully) in the answer. Except for the literal "_is there any such good code_" -- nobody can answer that, except for those who may have written it. But then this would be a poll. As for an SO-style question -- I think that there is _very_ little chance that there could be a good systemic use of chaining `=>`. – zdim Sep 12 '16 at 06:46
  • The "glitch" at the end of a chain can be "fixed" by adding an arrow. For example: my @a_list = (a => b => c =>); – mp3 Sep 14 '16 at 00:21
  • @mp3 I take this Q to be a quest about potential uses of a feature, among others for designing interfaces. An idea of denoting a graph by `a => b => c` is nice, it's clean and clear. But I find a problem with such uses since it relies on a particular syntax for a mere list, and to me it would appear unclear why. In that sense, the fact that the last element is unquoted and needs quotes `'c'` is a "glitch" in its neatness, and emphasizes the question of why it is used. So it goes to (my thoughts on) what a good interface is. I am not sure that adding a loose `=>` improves on that. – zdim Sep 14 '16 at 05:53
  • @zdim: the whole point about using syntax like $k4=Graph([a=>b=>c=>d=>a=>c=>],[b=>d=>]) for a "mere list" is that it MIGHT be a teensy little bit more readable, essentially providing a comment about the arc direction for a directed graph. But I agree: Graph([a,b,c,d,a,c],[b,d]) looks better - I am not comfortable with terminating => (and I wish that Perl would warn about that). And I emphasize - the mere list is usually based right away to a constructor that creates the real data structure. Now, Path(a=>b=>c=>) looks more reasonable. – Krazy Glew Sep 15 '16 at 02:50
  • @mp3: darn, you are right - but it sure does look strange to have an arrow pointing to nothing, Plus it forces you a level deeper for a graph: K4 with undef works flat (a=>b=>c=>d=>a=>c=>undef,b=>d=>undef); K4 with terminal => doesn't (a=>b=>c=>d=>a=>c=>,b=>d=>) and needs extra depth K4 ([a=>b=>c=>d=>a=>c=>],[b=>d=>]), in which case the dangling => has not won you anything, just moved the ugliness around like toothpaste. – Krazy Glew Sep 15 '16 at 02:50
  • @mp3: you should create an answer for terminal =>, i.e. (a=>b=>c=>), and also include EVIL PERL TRICK print I=>Like=>Barewords=>; unless, of course, you don't want to be associated with it. // I have often thought that the reason that we don't actually use Perl as our interactive shell like bash is that bash defaults to barewords, whereas Perl usually requires quotes. – Krazy Glew Sep 15 '16 at 02:54
  • 1
    @KrazyGlew I think I will decline on grounds of not encouraging the behavior. I love that you can have commas on the end of a list for those times when you format code with one list item per line (or one key/value) pair per line. However, I can't think of a good time to do the same with a fat comma. I was just pointing it out because it is interesting. – mp3 Sep 15 '16 at 09:20
  • @KrazyGlew I understand what the "_whole point_" is. That's what my post and all comments are about. I just happen to think that chained fatties in general don't fit the purpose all that well. I find it to be _not_ more readable altogether and to slightly abuse syntactic liberties (rope) that Perl gives. – zdim Sep 15 '16 at 20:13
0

The fat comma here is not the problem. It's a comma that auto-quotes it's lefthand side if the lefthand side is a bareword that can be an identifier. That's it; there's no more magic. You would have encountered the same bug without it.

I suspect that your actual problem is that you are applying ideas from another language (Python?) to Perl and your mental model is wrong. Perl isn't fancy enough to make keyword arguments on its own. You deal with that by processing it yourself in the subroutine.

Here's your test program:

use strict; use warnings; 
sub kwargs_func{ print "inside\n"; my %kw = $_[0] ;}; 
kwargs_func( a => b => 1 )

You are always going to get a warning no matter how many arguments you pass because you try to assign a single item to a list. That $_[0] is just the first argument, which is merely a. The fat comma has nothing to do with it; you'll have the same problem no matter how you specify the arguments.

Even if you changed that $_[0] to the full argument list, @_, you still have an odd number of elements in your example.

The fat comma is good for associating arguments with each other. Consider Mojo::UserAgent for example:

my $tx = $ua->post('https://example.com' => json => {top => 'secret'});

I tend to write this with on separate lines to more easily see the things that go together:

   my $tx = $ua->post(
      $url,
      json => {top => 'secret'},
      );
brian d foy
  • 129,424
  • 31
  • 207
  • 592
-1

---+ BRIEF

In addition to notation for graphs and paths (like Travelling Salesman, or critical path), multiple serial fat arrow/commas can be nice syntactic sugar for functions that you might call like

# Writing: creating $node->{a}->{b}->{c} if it does not already exist
assign_to_path($node=>a=>b=>c=>"value"); 

# Reading
my $cvalue = follow_path($node=>a=>b=>c=>"default value);

the latter being similar to

my $cvalue = ($node->{a}->{b}->{c})//"default value);

although you can do more stuff in a pointer chasing / hashref path following function than you can with //

It turned out that I already had such functions in my personal library, but I did not know that you could use a=>b=>"value" with them to make them look less ugly where used.

---+ DETAIL

I usually try not to answer my own questions on this forum, encouraging others to - but in this case, in addition to the contrived example I posted inside and shortly after the original question, I have since realized what I think is a completely legitimate use for multiple fat arrow/commas in series.

I would not complain if multiple fat arrows in series were disallowed, since they are quite often a real bug, but there are at least two places where they are appropriate.

(1) Entering Graphs as Chains

Reminder: my first, totally contrived, use case for multiple fat pointer/commas in series was to make it easier to enter certain graphs by using "chains". E.g. a classic deadlock graph would be, in pairs { 1=>2, 2=>1 }, and as a "chain" (1=>2=>1). If you want to show a graph that is one big cycle with a "chord" or shortcut, it might look like ([1=>2=>3=>4=>5=>6=>1],[3=>6]).

Note that I used node numbers: if I wanted to use node names, I might have to do (a=>b=>c=>undef) to avoid having to quote the last node in a cycle (a=>b=>"c"). This is because of the implicit quote on the left hand but not the right hand argument. Since you have to but up with undef to support node names anyway, one might just "flatten" ([1=>2=>3=>4=>5=>6=>1],[3=>6]) to ([1=>2=>3=>4=>5=>6=>1=>undef,3=>6=>undef). In the former end of chain is indicated by end of array [...]. In the latter, by undef. Using undef makes all of the nodes at the left hand of a =>, so syntactically uniform.

I admit that tis is contrived - it was just the first thing that came to mind.

(2) Paths as a data type

Slightly less contrived: imagine that you are writing, using, or testing code that is seeking "paths" through a graph - e.g. Hamiltonians, Traveling Salesman, mapping, electronic circuit speed path analysis. For that matter, any critical path analysis, or data flow analysis.

I have worked in 4 of the 6 areas I just listed. Although I have never used Perl fat arrow/commas in such code (usually Perl is to slow for such code when I have been working on such tasks), I can certainly avow that, although it is GOOD ENOUGH to write (a,b,c,d,e) in a computer program, in my own notes I usually draw arrows (a->b->c->d->e). I think that it would be quite pleasant to be able to code it as (a=>b=>c=>d=>e=>undef), even with the ugly undefs. (a=>b=>c=>d=>e=>undef) is preferable to qw(a b c d e), if I were trying to make the code resemble my thinking.

"Trying to make the code resemble my thinking" is often what I am doing. I want to use the notations common to the problem area. Sometimes I will use a DSL, sometimes write my own, sometimes just write some string or text parsing routines But if a language like Perl has a syntax that looks almost familiar, that's less code to write.

By the way, in C++ I often express chains or paths as

Path p = Path()->start("a")->link_to("b")->link_to("c")->end("d");

This is unfortunately verbose, but it is almost self-explanatory.

Of course, such notations are just the programmer API: the actual data strcture is usually well hidden, and is seldom the linear linked list that the above implies.

Anyway - if I need to write such "path-manipulating" code in Perl, I may use (a=>b=>c=>undef) as a notation -- particularly when passed to a constructor like Path(a=>b=>c=>undef) which creates the actual data structure.

There might even be some slightly more pleasant ways of dealing with the non-quoting of the fit arrow/comma's right hand side: eg. sometimes I might use a code like 0 or -1 to indicate closed loops (cycles) or paths that are not yet complete: Path(a=>b=>c=>0) is a cycle, Path(a=>b=>c=>-1) is not. 0 rather looks like a closed loop. It is unfortunate that this would mean that you could not have numeric nodes. Or one might leverage more Perl syntax: Path(a=>b=>c=>undef), Path(a=>b=>c=>[]), Path(a=>b=>c=>{}).

All we are doing here is using the syntax of the programming language to create notations that resemble the notation of the problem domain.

(3) Finally, a use case that is more "native Perl"-ish.

Have you ever wanted to access $node->{a}->{b}->{c}, when it is not guaranteed that all of the elements of the path exist?

Sometimes one ends up writing code like

When writing:

$node = {} if not defined $node;
$node->{a} = {}  if not exists $node->{a};
$node->{a}->{b} = {}  if not exists $node->{a}->{b};
$node->{a}->{b}->{c} = 0;

When reading ... well, you can imagine. Before the introduction of the // operator, I would have been too lazy to enter it. With the // operator, such code might look like:

my $value = $node->{a}->{b}->{c}//"default value if the path is incomplete";

Yeah, yeah... one should never expose that much detail of the datastructure. Before writing code like the above, one should refactor to a nice set of object oriented APIs. Etc.

Nevertheless, when you have to deal with somebody else's Perl code, you may run into the above. Especially if that somebody else was an EE in a hurry, not a CS major.

Anyway: I have long had in my personal Perl library functions that encapsulate the above.

Historically, these have looked like:

assign_to_hash_path( $node, "a", "b", "c", 0 )
# sets $node->{a}->{b}->{c} = 0, creating all nodes as necessary
# can follow or create arbitrarily log chains
# the first argument is the base node,
# the last is the value
# any number of intermediate nodes are allowed.

or, more obviously an assignment:

${hash_path_lhs( $node, "a", "b", "c")} = 0
# IIRC this is how I created a left-hand-side
# by returning a ref that I then dereffed.

and for reading (now usually // for simple cases):

my $cvalue = follow_hash_path_undef_if_cannot( $node, "a", "b", "c" );

Since the simple case of reading is now usually //, it is worth mentioning less simple cases, e.g. in a simulator where you are creating (create, zero-fill, or copy-on-read), or possibly tracking stats or modifying state like LRU or history

my $cvalue = lookup( $bpred_top => path_history => $path_hash => undef );    
my $cvalue = lookup( $bpred_top => gshare => hash($pc,$tnt_history) => undef );    

Basically, these libraries are the // operator on steroids, with a wider selection of what to do is the full path does not exist (or even if it does exist, e.g. count stats and cache).

They are slightly more pleasant using the quote operators, e.g.

assign_to_hash_path( $node, qw{a b c}, 0);
${hash_path_lhs( $node, qw{a b c})} = 0;
my $cvalue = follow_hash_path_undef_if_cannot( $node, qw{a b c});

But now that it has sunk into my thick head after many years of using perlobj, I think that fat arrow/commas may make these look much more pleasant:

assign_to_hash_path( $node => a => b => c => 0);
my $cvalue = follow_hash_path( $node => a => b => c => undef );

Unfortunately, the LHS function doesn't improve much because of the need to quote the last element of such a path:

${hash_path_lhs( $node=>a=>b=>"c"} = 0;
${hash_path_lhs( $node=>a=>b=>c=>undef} = 0;

so I would be tempted to give up on LHS, or use some mandatory final argument, like

${hash_path_lhs( $node=>a=>b=>c, Create_As_Needed() ) = 0;
${hash_path_lhs( $node=>a=>b=>c, Die_if_Path_Incomplete() ) = 0;

The LHS code looks ugly, but the other two look pretty good, expecting that the final element of such a chain would either be the value to be assigned, or the default value.

assign_to_hash_path( $node => a => b => c => "value-to-be-assigned");
my $cvalue = follow_hash_path( $node => a => b => c => "default-value" );

Unfortunately, there is no obvious place to hand keyword options - the following does not work because you cannot distinguish optional keywords from args, at either beginning or end:

assign_to_hash_path( $node => a => b => c => 0);
assign_to_hash_path( {warn_if_path_incomplete=>1}, $node => a => b => c => 0);
my $cvalue = follow_hash_path( $node => a => b => c => undef );
my $cvalue = follow_hash_path( $node => a => b => c => undef, {die_if_path_incomplete=>1} );

I have occasionally used a Keyword class, abbreviated KW, so that a type inquiry can tell us which is the keyword, but that is suboptimal - actually, it's not bad, but it is just that Perl has no single BKM (yeah, TMTOWTDI):

assign_to_hash_path( $node => a => b => c => 0);
assign_to_hash_path( KW(warn_if_path_incomplete=>1), $node => a => b => c => 0);
my $cvalue = follow_hash_path( $node => a => b => c => undef );
my $cvalue = follow_hash_path( KW(die_if_path_incomplete=>1), $node => a => b => c => undef );
my $value = follow_hash_path( $node => a => b => c => undef, KW(die_if_path_incomplete=>1) );

Conclusion: Foo(a=>b=>c=>1) seems strange, but might be useful/nice syntactic sugar

So: while I do rather wish that use warnings had warned me about foo(a=>a=>1), when a keyword was duplicated by accident, I think that multiple fat arrow/commas in series might be useful in making some types of code more readable.

Although I haven't seen any real-world examples of this, usually if I can imagine something, a better and more perspicacious Perl programmer has already written it.

And I am considering reworking some of my legacy libraries to use it. In fact, I may not have to rework - the library that I designed to be called as

assign_to_hash_path( $node, "a", "b", "c", 0 )

may already work if invoked as

assign_to_hash_path( $node => a => b=> c => 0 )

Simple Working Example

For grins, an example of a simple path following function, that does a bit more error reporting than is convenient to do with //

$ bash 1278 $>  cat example-Follow_Hashref_Path.pl
use strict;
use warnings;

sub follow_path {
    my $node=shift;
    if( ref $node ne 'HASH' ) {
    print "Error: expected \$node to be a ref HASH,"
      ." instead got ".(
          ref $node eq ''
        ?"scalar $node"
        :"ref ".(ref $node))
      ."\n";
    return;
    }
    my $path=q{node=>};
    my $full_path = $path . join('=>',@_);
    foreach my $field ( @_ ) {
    $path.="->{$field}";
    if( not exists $node->{$field} ) {
        print "stopped at path element $field"
          ."\n    full_path = $full_path"
          ."\n    path so far = $path"
          ."\n";
        return;
    }
    $node = $node->{$field}
    }
}

my $node={a=>{b=>{c=>{}}}};

follow_path($node=>a=>b=>c=>"end");
follow_path($node=>A=>b=>c=>"end");
follow_path($node=>a=>B=>c=>"end");
follow_path($node=>a=>b=>C=>"end");
follow_path({}=>a=>b=>c=>"end");
follow_path(undef=>a=>b=>c=>"end");
follow_path('string-value'=>a=>b=>c=>"end");
follow_path('42'=>a=>b=>c=>"end");
follow_path([]=>a=>b=>c=>"end");

and use:

$ perl example-Follow_Hashref_Path.pl
stopped at path element end
    full_path = node=>a=>b=>c=>end
    path so far = node=>->{a}->{b}->{c}->{end}
stopped at path element A
    full_path = node=>A=>b=>c=>end
    path so far = node=>->{A}
stopped at path element B
    full_path = node=>a=>B=>c=>end
    path so far = node=>->{a}->{B}
stopped at path element C
    full_path = node=>a=>b=>C=>end
    path so far = node=>->{a}->{b}->{C}
stopped at path element a
    full_path = node=>a=>b=>c=>end
    path so far = node=>->{a}
Error: expected $node to be a ref HASH, instead got scalar undef
Error: expected $node to be a ref HASH, instead got scalar string-value
Error: expected $node to be a ref HASH, instead got scalar 42
Error: expected $node to be a ref HASH, instead got ref ARRAY
✓
$

Another Example ($node->{a}->{B}->{c}//"premature end")

$ bash 1291 $>  perl -e 'use warnings;my $node={a=>{b=>{c=>"end"}}}; print "followed path to the ".($node->{a}->{B}->{c}//"premature end")."\n"'
followed path to the premature end
$ bash 1292 $>  perl -e 'use warnings;my $node={a=>{b=>{c=>"end"}}}; print "followed path to the ".($node->{a}->{b}->{c}//"premature end")."\n"'
followed path to the end

I admit that I have trouble keeping the binding strength of // in my head.

Finally

By the way, if anyone has examples of idioms using // and -> that avoid the need to create library functions, especially for writes, I'd love to hear of them.

It's good to be able to create libraries to make stuff easier or more pleasant.

It is also good not to need to do so - as in ($node->{a}->{B}->{c}//"default").

Krazy Glew
  • 7,210
  • 2
  • 49
  • 62
  • I like `path(a => b => $val)` -- that one is clear, the path leading to a value. It also nicely hides the implementation. But there is a whole lot already available in Perl that works for your purpose, too. – zdim Sep 15 '16 at 19:58
  • 1
    With your C++ practice, why not do either (1) `$path->{a}->{b} = $val` -- use [`autovivification`](http://search.cpan.org/~vpit/autovivification-0.16/lib/autovivification.pm), pragma to suppress it, or (2) write a Perl class that does the same, with any syntax you want to introduce. – zdim Sep 15 '16 at 19:59
  • 1
    See [`this article`](https://www.effectiveperlprogramming.com/2011/07/turn-off-auto-vivification-when-you-dont-want-it/) for `autovivification` discussion. Also see core [`Hash::Util`](http://perldoc.perl.org/Hash/Util.html), choke full of goodies. Also, see for example [`this post`](http://stackoverflow.com/questions/790516/how-do-i-disable-autovivification-in-perl), also mentioning `tie`-ed data structures, via [`Tie-StrictHash`](http://search.cpan.org/~kvail/Tie-StrictHash-1.0/StrictHash.pm) There are indeed nicer ways than getting entangled with overly elaborated uses of `//` and such. – zdim Sep 15 '16 at 20:04
  • @zdim: thanks for the autovivification refs. (Voted up.) But in all of these things, I look to have the fewest external dependencies possible, and use code that works in as many old Perl versions as possible. Many of my libraries get reused in other projects, and they don't like having to import multiple other libraries, or upgrade to a new version of Perl. – Krazy Glew Sep 17 '16 at 02:05
  • @zdim: thanks again for pointing me to the autovivification pragma; however, I prefer to use it as `no autovivification` or `no autovivification qw(strict)`. I am okay on autovivification being used on true LHS, but I strongly object to autovivifying in someihing like `print '$a->{b}->{c} does not exist' if not exists $a->{b}->{c}` --- something that looks read-only should not modify the data structure. Perl5 autovivification is done in too many contexts. It is a language bug. – Krazy Glew Dec 20 '16 at 00:18
-1

Not only did I answer my own question, but I am also going to give it a separate second answer! :-}{

Anyway, today, a couple of months after I asked the original question, I was staring at some code and thinking to myself 'Boy, I wish that Perl had a prefix field accessor'.

Perl's standard hashref field accessor is a suffix, or postfix: $hashref->{fieldname}.

Some languages have a prefix field accessor:

In an effort to be Perl-like, might imagine writing this as {fieldname}<-$hashref

Although when I have seen it done it has been more verbose:

POSTFIX:   field of object
PREFIX:    object 's field

(Yeah, 's was an operator in that language. No, not COBOL - I think it was POP-4, and also possible Algol-68, where not built in but could be written)

compare to

PERL:      $object->{field}
C, etc:    object.field
           object->field

And then I realized that you can write a not-too-objectionable prefix field getter in perl

sub get_field ($%) {
    my $fieldname = shift;
    my $hash = shift;
    return $hash->{$fieldname};
}

# used
assert( 1 == get_field 'fieldname', $some->long_and_complex_function(with,args) )

or, pleasantly using fat commas to avoid the need for string quotes on the fieldname

assert( 1 == get_field fieldname=>$some->long_and_complex_function(with,args) )

comparing to

assert( 1 == $some->long_and_complex_function(with,args)->{fieldname} )

OK, so that's just an (ab)use of a single fat comma.

But obviously it can extend to

assert( 1 == get_field 3rd=>2nd=>1st=>$object )

which is equivalent to

assert( 1 == $object->{1st}->{2nd}->{3rd} )

I am not sure that I approve. But...

Krazy Glew
  • 7,210
  • 2
  • 49
  • 62