Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Existing module for PerlX::Maybe except for hash existence?

by Corion (Patriarch)
on Feb 21, 2025 at 16:51 UTC ( [id://11164052]=perlquestion: print w/replies, xml ) Need Help??

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

I'm quite fond of using PerlX::Maybe to pass parameters to subroutines if they are there:

GetOptions( 'f' => \my $filename, 'bar' => \my $bar, 'bruce' => \my $batman, 'u' => \my $universe, ); foo( file => $filename, maybe bar => $bar, maybe baz => $batman, maybe universe => $universe, )

The above will invoke foo() with only the hash keys that have a defined value. This is better in the sense that this allows foo() to make a difference between parameter was not passed at all and parameter was passed, but was undef:

sub foo( %options ) { if( ! exists $options{ baz } ) { $options{ baz } = 'Superman'; }; ... }

Now, I'd like to have something similar(ish) to maybe on the receiving side for subroutines and objects, but I'm lacking a good name and a good syntax for it. The idea is to only set a value in a hash if the key does not exist yet. This is different from the // operator, because that one only checks for defined-ness, not for existence.

If we already had full-grown subroutine keyword parameters, this could be written in a declarative way as:

sub foo( :$baz = 'Superman', :$bar, :$file, :$universe='DC' ) { }

(actually, I'm not sure if the above is correct for an invocation foo( baz => undef )).

But we don't have named parameters in the syntax yet, so we have to deparse the parameters ourselves:

sub foo( %options ) { ... }

Let's assume that option is a good name for this (I'm not convinced):

sub option ( $key, $def, $options ) { if( !exists $options->{ $key } ) { $options->{ $key } = $def; } return $options }

Then we could have a syntax like this:

sub foo( %options ) { option baz => 'Superman', option universe => 'DC', \%options; return \%options }

But I'm not entirely happy with this approach for two reasons:

  1. It doesn't strike me as overly elegant. I'm happy with it being not overly elegant, but there are some warts:
    • We pass in and modify the %options hash by reference. Alternatively we could do %options = option foo => 'bar', %options;, but that copies the whole hash a lot of times
    • We eagerly evaluate the default values, which might be costly in the case of function calls:
      option files => [glob('~/*')], option html => $user_agent->get('https://example.com'),
  2. There must be something like this on CPAN already

Test file:

use 5.020; use feature 'signatures'; no warnings 'experimental::signatures'; use Carp 'croak'; sub option ( $key, $def, $options ) { if( !exists $options->{ $key } ) { $options->{ $key } = $def; } return $options } sub required ( $key, $options ) { if( !exists $options->{ $key } ) { croak sprintf 'Need the "%s" option passed in', $key; } return $options } sub foo( %options ) { option baz => 'Superman', option universe => 'DC', \%options; return \%options } sub req( %options ) { required 'run', option baz => 'Superman', \%options; return \%options } use Test2::V0 '-no_srand'; is option('baz' => 'default', {}), { baz => 'default' }, 'Setting a de +fault'; is foo(), { baz => 'Superman', universe => 'DC' }, 'Default values'; is foo(baz => 0), { baz => '0', universe => 'DC' }, 'False value'; is foo(baz => undef), { baz => undef, universe => 'DC' }, 'Undef but p +resent value is kept'; is foo(baz => undef), { baz => undef, universe => 'DC' }, 'Undef but p +resent value is kept'; ok dies { req( baz => 'Wonderwoman' ) }, 'A missing obligatory option +dies'; ok lives { req( run => undef, baz => 'Wonderwoman' ) }, 'Passing the o +bligatory option lives, even if undefined'; #class cfoo 0.01 { # field $baz; # field $universe; # # method foo( $self, %options ) { # option baz => $self->baz, # option universe => $self->universe, # \%options; # # return \%options # } #} #my $f = cfoo->new( baz => 'objSuperman', universe => 'DC' ); #is $f->foo( baz => undef ), { baz => undef, universe => 'meth_DC' }, +'Positional parameters work'; done_testing;

Replies are listed 'Best First'.
Re: Existing module for PerlX::Maybe except for hash existence?
by haj (Vicar) on Feb 22, 2025 at 23:23 UTC

    This is a rant, slightly off-topic.

    I find distinguishing between "a parameter was not provided" and "a parameter was given as undef" icky. Both are saying "The caller does not provide a defined value", but there is no hint why you need two different ways to say that, and what different behaviors you would expect. Do you expect undefined behavior when you pass an undef value? Probably not. And where does that stop? I recall a suggestion (by Ovid?) to add another special value "uninitialized", so if you say my $v = undef; then $v would be not uninitialized but undefined, and if you say my $v; then $v is both undefined and uninitalized. This "uninitialized" would be the scalar equivalent of the value of a non-existing hash key. Sooner or later someone will call for a builtin function which resets a variable to the uninitialized state...

    When I define the signature of a subroutine I need to specify what values I expect, and what behavior will result. I can offer one default value for convenience. But ... why would I want to provide two different ways of not passing a value? If these two ways mean different things to the subroutine, then there must be a better way to describe the resulting behavior.

    I understand PerlX::Maybe as a necessary evil to cope with icky interfaces which make a decision between "undefined" and "not provided", but you are looking for something "on the receiving side for subroutines and objects" to which I would like to reply: Please don't.

      I tend to agree that differentiating between "argument not passed" and "argument is undef" is tricky, and from experience with my own modules there is a simple situation where it is problematic: un-setting individual arguments later. You can say function(%defaults, option=>undef) to override $defaults{option} back to undef, but removing a key from %defaults is not really a viable option when, just for example, the defaults and overriding options come from JSON files.

        This point also seems to be evidence for treating "not existing" and "not defined" as the same, since passing an explicit `undef` there would restore that `function` to "use a default value for `option`".

      Both are saying "The caller does not provide a defined value", but there is no hint why you need two different ways to say that

      My guess is that it's the difference "the caller does not have/want a value for this" and "the caller forgot to specify the value"

      Take one example: A program called SchrödingersCat needs a command line flag to specify if the cat is alive or dead. But when called with a value of "undef", the caller provides a valid value of "we don't know if the cat is alive or dead". See Tom Scott's video on Null Island: The Busiest Place That Doesn't Exist

      PerlMonks XP is useless? Not anymore: XPD - Do more with your PerlMonks XP
      Also check out my sisters artwork and my weekly webcomics
        Take one example: A program called SchrödingersCat needs a command line flag to specify if the cat is alive or dead. But when called with a value of "undef", ...

        A nice example, indeed. Just one question: How do you pass an undefined value as a command line flag?

        See Tom Scott's video....

        Thanks: Another example which underpins my point. The video explains the difference between NULL, "NULL" and 0 when read from a database. So yes, a database has NULL as the equivalent of an undefined value. But a database has no notion of "this field does not exist": If a field is in a table definition, then it exists for every single record. So, again, it has only one special value, not two.

        Your example fails.

        There are three states: Alive, dead, superposition.

        There's no need to differentiate between not provided and undef. Both would be considered superposition.

        And you mention command-line arguments where it's not even possible to pass undef, completely defying your point.

        Your example supports haj, not you.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (4)
As of 2025-03-26 08:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    When you first encountered Perl, which feature amazed you the most?










    Results (67 votes). Check out past polls.

    Notices?
    erzuuliAnonymous Monks are no longer allowed to use Super Search, due to an excessive use of this resource by robots.