Is that a Perl 6 Hash or Block?

2020-02-28 03:42发布

问题:

This is a bit of unexpected behavior that's likely to bite beginners. First, is this intended? Second, what other things does Perl 6 use to guess which object to create? Does it start off thinking it's Block or Hash and change later, or does it decide on the end?

You can construct a Hash with braces and the fat arrow:

my $color-name-to-rgb = {
    'red' => 'FF0000',
    };

put $color-name-to-rgb.^name;  # Hash

Using the other Pair notation creates a Hash too.

my $color-name-to-rgb = {
    :red('FF0000'),
    };

But, absent the fat arrow, I get a Block instead:

my $color-name-to-rgb = {
    'red', 'FF0000',
    };

put $color-name-to-rgb.^name;  # Block

The Hash docs only mention that using $_ inside the braces creates a Block.

There are other ways to define a hash, but I'm asking about this particular bit of syntax and not looking for the workarounds I already know about.

$ perl6 -v
This is Rakudo version 2017.04.3 built on MoarVM version 2017.04-53-g66c6dda
implementing Perl 6.c.

回答1:

TL;DR This answer starts with the simplest description I think is reasonable then attempts to cover everything in precise detail. Please let me know in the comments if anything is too complicated or missing.

This rule only applies in term position

Only braced code ({...}) that is in term position1 is subject to this DWIM/WAT.2

In these examples the {...} is not in term position:

if True { ... }   # always a block
class foo { ... } # always a package
put bar{ ... }    # always a hash index

For the rest of this answer {...} only means braced code in term position.

Simple description of DWIM/WAT for {...} in term position

  1. By default {...} is a Block. Also, if it has a signature, multiple statements, or a declaration of an identifier, then it's a Block.

  2. Otherwise, if {...} contains just a list of zero or more values starting directly with a % sigil'd variable (eg %foo) or a Pair literal (eg :bar) then it's a Hash.

An explicit signature means it's a Block

Some braced code has an explicit signature, i.e. it has explicit parameters such as $foo below. It always constructs a Block no matter what's inside the braces:

say WHAT         { key => $foo, 'a', 'b' } # (Hash)
say WHAT -> $foo { key => $foo, 'a', 'b' } # (Block)

An implicit signature also means it's a Block

Some braced code has an implicit signature:

  • Explicit use of a "pronoun" inside braced code means it's a Block with an implicit signature if it doesn't already have an explicit signature. The pronouns are $_, @_, and %_.

  • Implicit use of $_ inside braced code via a method call without an explicit invocant means it's a Block with an implicit signature. In other words, even { .foo } has a signature ((;; $_? is raw)) due to the .foo.

  • Use of an explicit "placeholder" variable (e.g. $^foo) inside braced code also means it's a Block with an implicit signature (with a $foo parameter in it).

As with an explicit signature, if braced code has an implicit signature then it always constructs a Block no matter what's inside the braces:

say WHAT { key => $_ }                     # (Block)
say WHAT { key => 'value', .foo, .foo }    # (Block)

Multiple top level statements mean it's a Block

say WHAT { :foo; (do 'a'), (do 'b') }     # (Block)
say WHAT { :foo, (do 'a'), (do 'b') }     # (Hash)

The second line contains multiple statements but they're producing values within a list that's the single top level statement.

A top level declaration of an identifier mean it's a Block

say WHAT { :foo, (my $bar), $baz }        # (Block)
say WHAT { :foo, {my $bar}, $baz }        # (Hash)

The last line contains a Block as a key that contains a declaration (my $bar). But that declaration belongs to the inner Block, not the outer braced code. So the inner Block is just a value as far as the outer braced code is concerned, and thus the outer braced block is still interpreted as a Hash.

Still Blocks

my $bar = key => 'value';
say WHAT         { $bar, %baz }            # (Block)
say WHAT         { |%baz      }            # (Block)
say WHAT         { %@quux     }            # (Block)

None of the above start with a % sigil'd variable or literal pair.

When it's a Hash

Braced code that doesn't fall into any of the foregoing situations that force it to be a Block, and which is just a list of zero or more values that start with either a % sigil'd variable or a Pair literal constructs a Hash:

say WHAT { %foo      }                    # (Hash)
say WHAT { %foo, ... }                    # (Hash)
say WHAT { foo => 42, ... }               # (Hash)
say WHAT { :foo, ... }                    # (Hash)
say WHAT { key => $foo,  'a', 'b'      }  # (Hash)
say WHAT { 'a', 'b',      key => $foo  }  # (Block)

The last block above doesn't start with a %foo or literal pair.

say WHAT { %foo,         'a', 'b'      }   # (Hash)
say WHAT { Pair.new('key',$foo),           # Pair.new is NOT a literal
           'a', 'b'                    }   # (Block)

The second block above doesn't start with a %foo or literal pair.

To force Block or Hash interpretation

  • To write an empty Block, write {;}. To write an empty Hash, write {}.

  • To force braced code to construct a Block instead of a Hash, write a ; at the start i.e. {; ... }.

  • To force braced code to construct a Hash instead of a Block, follow the rules explained above or write %(...) instead of {...}.

Footnotes

1 See comments below answer about term position.

2 A worthwhile DWIM must work in most coders' favor most of the time and its associated WATs must be acceptable to the community. (WAT refers to a dev's surprise when a DWIM doesn't do what they meant. For every DWIM there are one or more WATs.)

Acceptable WATs have barks worse than their bites. Like all DWIMs, this one can require community vigilance and response to ensure it remains a net win rather than warranting deprecation, and community member opinions differ on how much its bite hurts. cf my perspective vs Sam's answer to this question.



回答2:

The preferred Perl6 way is to use %( ) to create hashes.

my $color-name-to-rgb = %(
    'red', 'FF0000',
    );

I would not recommend people use braces to create hashes, ever. If they want to make a hash then %( ) is the proper way to do it.

If you are coming from the Perl 5 world it's best to just get in the habit of using %( ) instead of { } when creating a Hash.



标签: hash block raku