Is that one argument or none for a Perl 6 block?

2019-04-08 04:53发布

What is the Perl 6 way to tell the difference between an argument and no argument in a block with no explicit signature? I don't have any practical use for this, but I'm curious.

A block with no explicit signature puts the value into $_:

my &block := { put "The argument was $_" };

The signature is actually ;; $_? is raw. That's one optional argument. The @_ variable isn't defined in the block because there is no explicit signature.

There's the no argument, where $_ will be undefined:

&block();  # no argument

But there's also a one argument situation where $_ will be undefined. A type object is always undefined:

&block(Int);

But, an $_ with nothing in it is actually an Any (rather than, say, Nil). I can't tell the difference between these two cases:

&block();
&block(Any);

Here's a longer example:

my $block := {
    say "\t.perl is {$_.perl}";

    if $_ ~~ Nil {
        put "\tArgument is Nil"
        }
    elsif ! .defined and $_.^name eq 'Any' {
        put "\tArgument is an Any type object"
        }
    elsif $_ ~~ Any {
        put "\tArgument is {$_.^name} type object"
        }
    else {
        put "\tArgument is $_";
        }
    };

put "No argument: ";    $block();
put "Empty argument: "; $block(Empty);
put "Nil argument: ";   $block(Nil);
put "Any argument: ";   $block(Any);
put "Int argument: ";   $block(Int);

Notice the no argument and Any argument forms show the same things:

No argument:
    .perl is Any
    Argument is an Any type object
Empty argument:
    .perl is Empty
    Argument is Slip type object
Nil argument:
    .perl is Nil
    Argument is Nil
Any argument:
    .perl is Any
    Argument is an Any type object
Int argument:
    .perl is Int
    Argument is Int type object

3条回答
倾城 Initia
2楼-- · 2019-04-08 05:10

As far as I know, the only way to know the number of parameters passed without an explicit signature, is to use @_ inside the body, which will generate a :(*@_) signature.

my &block := { say "Got @_.elems() parameter(s)" };
block;               # Got 0 parameter(s)
block 42;            # Got 1 parameter(s)
dd block.signature;  # :(*@_)

Yeah, the good old @_ is still there, if you want it :-)

查看更多
够拽才男人
3楼-- · 2019-04-08 05:18
{ put $_.perl }

Is sort of similar to this: (which doesn't work)

-> ;; $_? is raw = CALLERS::<$_> { put $_.perl }

Since the default is default for $_ outside of the block is Any, if you don't place anything into $_ before you call the function you get Any.


To get something at all similar where you can tell the difference use a Capture :

my &foo = -> ;; |C ($_? is raw) {
    unless C.elems {
       # pretend it was defined like the first Block above
       CALLER::<$_> := CALLER::CALLERS::<$_>
    }
    my $called-with-arguments := C.elems.Bool;


    if $called-with-arguments {
        say 'called with arguments';
    } else {
        say 'not called with arguments';
    }
}
查看更多
smile是对你的礼貌
4楼-- · 2019-04-08 05:34

Here's how I solved this. I'd love to do this in a cleaner way but the cleverness of the language gets in the way and I have to work around it. This works for positional parameters but there are deeper shenanigans for named parameters and I won't deal with those here.

I had another question, Why does constraining a Perl 6 named parameter to a definite value make it a required value?, where the answers clarified that there are actually no optional parameters. There are merely parameters that have a default value and that there is an implicit default value if I don't explicitly assign one.

The crux of my problem is that I want to know when I gave the parameter a value and when I didn't. I give it a value through an argument or an explicit default. An implicit default is a type object of the right type. That's Any if I didn't specify a type. That implicit default must satisfy any constraint I specify.

The first goal is to tightly constrain the values a user can supply when they call code. If an undefined value is not valid then they shouldn't be allowed to specify one.

The second goal is to easily distinguish special cases in the code. I want to reduce the amount of special knowledge some part of the deeper code needs to know.

I can get the third case (where I know there was no argument or suitable default) by explicitly assigning a special value that I know can't be anything other meaningful thing. There's a value that's even more meaningless than Any. That's Mu. It's the most undefined values of all undefined values. The Any is one of two subtypes of Mu (the other is Junction) but you should almost never see a Mu end up in one of your values in normal code. Undefined things in user code start at Any.

I can create a constraint that checks for the type I want or for Mu and set a default of Mu. If I see a Mu I know there was no argument and that it's Mu because my constraint set that.

Since I'm using Mu there are some things I can't do, like use the === operator. Smart matching won't work because I don't want to test the inheritance chain. I can check the object name directly:

my $block := ->
    $i where { $^a.^name eq 'Mu' or $^a ~~ Int:D } = Mu
    {
    say "\t.perl is {$i.perl}";

    put do given $i {
        when .^name eq 'Mu'  { "\tThere was no argument" }
        when Nil             { "\tArgument is Nil"       }
        when (! .defined and .^name eq 'Any') {
            "\tArgument is an Any type object"
            }
        when .defined {
            "\tArgument is defined {.^name}"
            }
        default { "\tArgument is {.^name}" }
        }
    };

put "No argument: ";         $block();
put "Empty argument: ";      try $block(Empty); # fails
put "Nil argument: ";        try $block(Nil);   # fails
put "Any type argument: ";   try $block(Any);   # fails
put "Int type argument: ";   try $block(Int);   # fails
put "Int type argument: ";   $block(5);

Now most of those invocations fail because they don't specify the right things.

If these were routines I could make multis for a small number of cases but that's an even worse solution in the end. If I had two parameters I'd need four multis. With three such parameters I'd need six. That's a lot of boilerplate code. But, blocks aren't routines so that's moot here.

查看更多
登录 后发表回答