jo37 has asked for the wisdom of the Perl Monks concerning the following question:

Dear Nuns and Monks,

after some digging into subroutine signatures I stumbled upon a little problem that seems to be non-existing without signatures: How to distinguish between a missing and an undefined argument? Without signatures it goes straight forward: shift arguments as long as there are any left over. There is no problem distinguishing between non-existing and undefined. See no_sig in the example below.

With signatures, a naive approach would be to check the size of @_, too. This appears to be some kind of illogical to me, see sig_std in the example.

Going one step further, I came to a lexical variable defined inside the signature that is set only in case of a missing argument. This apparently works but I'm not sure if this is indeed valid. The argument and the new variable are named alike to establish a connection between them. See sig_lexical in the example.

Is there a common idiom how to handle such case with signatures? I'm not satisfied with my approaches in the example.

Any other ideas?

#!/usr/bin/perl use v5.12; use Test2::V0; use experimental 'signatures'; sub show ($arg) { defined $arg ? $arg : 'undef'; } # No signature, no check. sub no_sig { my $what = shift; state $data; # Straight forward: shift off first arg if there is any. if (@_) { my $arg = shift; say "$what: ", show $arg; $data = $arg; } else { say "$what: ", show $data; return $data; } } # Standard signature with optional second arg. sub sig_std ($what, $arg=undef) { state $data; # Kind of illogical: there is no obvious connection between @_, it +s # size and $arg. if (@_ >= 2) { say "$what: ", show $arg; $data = $arg; } else { say "$what: ", show $data; return $data; } } # Signature with side effect on missing arg. sub sig_lexical ($what, $arg=(my $no_arg=1, undef)) { state $data; # $arg and $no_arg are (loosely) connected by their names. Howeve +r, # there seems to be no specification about lexical variables # declared within a signature. if (!$no_arg) { say "$what: ", show $arg; $data = $arg; } else { say "$what: ", show $data; return $data; } } pass 'init'; no strict 'refs'; for my $sub (qw(no_sig sig_std sig_lexical)) { # Set to 1 and retrieve: &$sub("$sub setter", 1); is &$sub("$sub getter"), 1, "$sub get 1"; # Set to undef and retrieve: &$sub("$sub setter", undef); is &$sub("$sub getter"), U(), "$sub get undef"; SKIP: { skip "arg check without signature" if $sub =~ /^no/; # Too many arguments: like dies {&$sub("$sub invalid args", 42, 1)}, qr/Too many arguments/, "$sub with three args"; } } done_testing; __DATA__ # Seeded srand with seed '20201227' from local date. ok 1 - init no_sig setter: 1 no_sig getter: 1 ok 2 - no_sig get 1 no_sig setter: undef no_sig getter: undef ok 3 - no_sig get undef ok 4 - skipped test # skip arg check without signature sig_std setter: 1 sig_std getter: 1 ok 5 - sig_std get 1 sig_std setter: undef sig_std getter: undef ok 6 - sig_std get undef ok 7 - sig_std with three args sig_lexical setter: 1 sig_lexical getter: 1 ok 8 - sig_lexical get 1 sig_lexical setter: undef sig_lexical getter: undef ok 9 - sig_lexical get undef ok 10 - sig_lexical with three args 1..10

Greetings,
-jo

$gryYup$d0ylprbpriprrYpkJl2xyl~rzg??P~5lp2hyl0p$

Replies are listed 'Best First'.
Re: Distinguish between missing and undefined arguments with subroutine signatures
by choroba (Cardinal) on Dec 27, 2020 at 17:26 UTC
    There is nothing like a missing argument with signatures:
    #!/usr/bin/perl use warnings; use strict; use experimental 'signatures'; use Syntax::Construct qw{ // }; sub show ($arg) { $arg // 'undef' } use Test::More; use Test::Exception; throws_ok { show() } qr/Too few arguments/; done_testing();
    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

      My question was about optional arguments.

      Greetings,
      -jo

      $gryYup$d0ylprbpriprrYpkJl2xyl~rzg??P~5lp2hyl0p$
        Oh, sorry, I didn't get it.

        Why do you need it?

        It seems you don't want an optional argument with a default value, you want a subroutine that knows whether setting the default value was triggered. It's hard to do it with just the optional argument, as it's a single value, but you're interested in two values: the value itself and the boolean telling whether the default value was triggered. Both your solutions introduce such a value (whether it's the scalar @_ or the additional lexical variable). I'm not sure which one I'd prefer, but I'd probably add a comment to both of them.

        map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
Re: Distinguish between missing and undefined arguments with subroutine signatures
by LanX (Saint) on Dec 27, 2020 at 18:52 UTC
    > Is there a common idiom how to handle such case with signatures?

    The rule of thumb is to use a default which is defined. °

    Using undef as default should be redundant and will force you to check @_ - if still possible (untested)

    Sorry, your code is longish (aka TLDR), if I didn't get it right, please consider condensing it to the relevant part.

    This question could be actually very relevant if there is a real use case! P5P is considering to drop the population of @_ when signatures are used.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

    update

    °) not

    # Standard signature with optional second arg. sub sig_std ($what, $arg=undef) {
      P5P is considering to drop the population of @_ when signatures are used
      But only after alternative facilities for argument instrospection have been provided. See the proposal on the p5p mailing list.

      In particular, in

      sub foo($x, ??$has_y, $y = 0) { ... }
      $has_y is true if and only if a second arg is passed to the sub.

      Dave.

        sub foo($x, ??$has_y, $y = 0) { ... }

        Great! That's it. (Or better: Will be)

        Greetings,
        -jo

        $gryYup$d0ylprbpriprrYpkJl2xyl~rzg??P~5lp2hyl0p$

      The use case I have in mind is a combined getter/setter for a perl object. Return the value when called without an additional arg, set the value when called with an arg. A call with an undefined arg clears the attribute - just like in my example.

      Maybe that's not a real world use case - I'm just playing with things.

      Greetings,
      -jo

      $gryYup$d0ylprbpriprrYpkJl2xyl~rzg??P~5lp2hyl0p$
        > combined getter/setter ... Return the value when called without an additional arg,

        from my experience are mutators written in a way to always return the value.

        > A call with an undefined arg clears the attribute - just like in my example.

        OK ... but in that case why do you have a default value at all?

        Anyway, if you want to allow to set to undef and don't wanna introspect @_ , then you're stuck in a kind of semipredicate problem.

        Such problems can be solved in Perl by setting a new "impossible"° object MISSING as default, like

        use constant MISSING => bless {}, "MyPackg::__Missing__"

        and later

        sub mutator ($value = MISSING) { ...BODY... }

        (untested)

        If you see that an arguments equals to that "impossible" value blessed into your own private namespace below "MyPackg", you can be sure that it wasn't used by accident.

        HTH! :)

        update

        clearer rewording, clearer code

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

        °) of course it's possible that such a value is used, but extremely unlikely without intend.

Re: Distinguish between missing and undefined arguments with subroutine signatures
by jo37 (Curate) on Jan 07, 2021 at 15:29 UTC

    Using LanX's suggestion in Re^3: Distinguish between missing and undefined arguments with subroutine signatures (semipredicate problem), I finally found a practicable solution to my problem.

    # Unique catcher for a missing argument use constant MISSING => bless {}, __PACKAGE__ . '::__missing__'; # Helper sub, that either checks its argument against the catcher # or otherwise returns the catcher itself. sub missing { @_ ? $_[0] && $_[0] eq MISSING : MISSING; } # To be used with signatures in various places: sub foo ($self, $val=missing) { if (missing $val) { # getter } else { # setter } }

    Thanks, Rolf!

    Greetings,
    -jo

    $gryYup$d0ylprbpriprrYpkJl2xyl~rzg??P~5lp2hyl0p$
      Your Welcome! :)

      But I suppose you don't want the sub missing() to be available as method the way ->foo() is?

      And while the MISSING object is a nice workaround for the semi-predicate problem, I still think it's an overkill here.

      Like I said you could just slurp into @val and have the full functionality, with less code:

      use strict; use warnings; use experimental "signatures"; use Carp; { package BLA; sub new ($class,@properties) { return bless {@properties}, $class; } sub foo ($self, @val) { unless (@val){ # getter return $self->{foo} } elsif (@val == 1) { # setter return $self->{foo} = $val[0]; } else { # ERROR local $" = ","; Carp::croak "Too many arguments for method 'BLA->foo(@val) +'"; } } } my $x = BLA->new(foo=>42); warn "getter:", $x->foo; warn "setter:", $x->foo(666); warn "getter:", $x->foo; warn "error:", $x->foo(42,666);

      -*- mode: compilation; default-directory: "d:/tmp/pm/" -*- Compilation started at Fri Jan 8 21:39:32 C:/Perl_524/bin\perl.exe -w d:/tmp/pm/missing_param.pl getter:42 at d:/tmp/pm/missing_param.pl line 31. setter:666 at d:/tmp/pm/missing_param.pl line 32. getter:666 at d:/tmp/pm/missing_param.pl line 33. Too many arguments for method 'BLA->foo(42,666)' at d:/tmp/pm/missing_ +param.pl line 34. Compilation exited abnormally with code 255 at Fri Jan 8 21:39:33

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

      updates

      improved code with croak and better error message

        But I suppose you don't want the sub missing() to be available as method the way ->foo() is?

        That doesn't harm at all. Calling $obj->missing just returns false, which is correct :-)

        However, your objection discovered a flaw in my approach. The missing sub needs to be prototyped to go one step further: multiple optional arguments. And then slurping starts hurting :-)

        This leads to:

        #!/usr/bin/perl use v5.16; use warnings FATAL => 'all'; package Foo; use experimental 'signatures'; use constant MISSING => bless {}, __PACKAGE__ . '::__missing__'; sub missing :prototype(;$) { @_ ? $_[0] && $_[0] eq MISSING : MISSING; } sub new ($class) { bless {}, $class; } sub foo ($self, $foo=missing, $bar=missing) { say "foo is missing" if missing $foo; say "bar is missing" if missing $bar; } package main; my $foo = Foo->new; say "none:"; $foo->foo; say "one:"; $foo->foo(1); say "two:"; $foo->foo(1, 2); print "\n"; say 'object foo is not missing' unless $foo->missing; __DATA__ none: foo is missing bar is missing one: bar is missing two: object foo is not missing

        Greetings,
        -jo

        $gryYup$d0ylprbpriprrYpkJl2xyl~rzg??P~5lp2hyl0p$