Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Distinguish between missing and undefined arguments with subroutine signatures

by jo37 (Hermit)
on Dec 27, 2020 at 17:04 UTC ( #11125794=perlquestion: print w/replies, xml ) Need Help??

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 (Archbishop) 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 (Sage) 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 (Hermit) 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$

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11125794]
Approved by LanX
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (2)
As of 2022-05-17 14:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Do you prefer to work remotely?



    Results (66 votes). Check out past polls.

    Notices?