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

3

6

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
Bataan answered 1/8, 2017 at 2:43 Comment(0)
B
1

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.

Bataan answered 10/1, 2018 at 0:59 Comment(4)
"Here's how I solved this." This answer uses a signature. The question says "What is the Perl 6 way to tell the difference between an argument and no argument in a block with no explicit signature?". This answer is also monstrously complex. Liz's answer is simple, correct, and applies for a block with no explicit signature.Imbibe
Liz's answer doesn't limit the number of arguments. I wanted to do it without a signature but couldn't find a way (as is often the case with questions and the final answer).Bataan
What's wrong with following Liz's suggestion and writing a guard like { fail unless +@_ == 1 } or whatever other scheme you want (eg given +@_ { when 0 { ... }; when 1 { ... }; default { fail } };)? And if that's still not good enough for some reason and you must use a signature, why not just -> |args where { +args == 1 or fail 'nope' } ($arg) { } or similar, following Brad's suggestion?Imbibe
My answer adequately answers your comments. Move on.Bataan
W
4

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 :-)

Whitebeam answered 1/8, 2017 at 7:14 Comment(3)
It's there is you don't want to value in $_. And the values in @_ are immutable.Bataan
"It's there is you don't want to value in $_." (By which I presume brian meant the value isn't in $_ if you refer to @_ in a block without an explicit signature.) Then just put it there. For example { $_ := .[0] if @_ }. Yes, it's cumbersome. But Liz's answer precisely nails what brian literally asked in his question ("What is the Perl 6 way to tell the difference between an argument and no argument in a block with no explicit signature?"). brian's point that this means you have to manually bind or assign $_ is a tiny nit compared with the monstrous complexity in his accepted answer.Imbibe
"the values in @_ are immutable". Only if what it binds to is immutable. The exact same thing applies to $_. Thus { $_++ }(42) results in caller postfix:<++> ... require[s] mutable argument. In my prior comment, just exchange a = for the := if you want the mutability.Imbibe
F
3
{ 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';
    }
}
Freeboard answered 1/8, 2017 at 5:47 Comment(3)
I know all that. I'm asking about the case where I put Any in it.Bataan
@briandfoy If you need this information, don't use a feature that is intended to make the difference invisible.Freeboard
It's not that I need it. I'm looking at how the language works and its boundaries so I can tell other people what they won't be able to do.Bataan
B
1

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.

Bataan answered 10/1, 2018 at 0:59 Comment(4)
"Here's how I solved this." This answer uses a signature. The question says "What is the Perl 6 way to tell the difference between an argument and no argument in a block with no explicit signature?". This answer is also monstrously complex. Liz's answer is simple, correct, and applies for a block with no explicit signature.Imbibe
Liz's answer doesn't limit the number of arguments. I wanted to do it without a signature but couldn't find a way (as is often the case with questions and the final answer).Bataan
What's wrong with following Liz's suggestion and writing a guard like { fail unless +@_ == 1 } or whatever other scheme you want (eg given +@_ { when 0 { ... }; when 1 { ... }; default { fail } };)? And if that's still not good enough for some reason and you must use a signature, why not just -> |args where { +args == 1 or fail 'nope' } ($arg) { } or similar, following Brad's suggestion?Imbibe
My answer adequately answers your comments. Move on.Bataan

© 2022 - 2024 — McMap. All rights reserved.