Suman
Suman

Reputation: 3545

How to check if named parameters are being passed within function body in Raku

I have this code with two functions, the second function has some named parameters with default values:

sub func1($x,$y) {
    # do something
}

sub some_func($x, $y , :$vel = 1, :$acceleration = 1/$vel) {
    # if  $vel and $acceleration both are not missing
    #    call func1($x, $y) 
    # else do something else
}

As we know the second function can be called in following ways:

some_func($x, $y); #vel and acceleration are missing

some_func($x, $y, vel => 2, acceleration => 5); #vel and acceleration are not missing

In second call, named parameters: vel and acceleration are passed. How can I check within the function body that these two named parameters are being passed?

Readings:

Keeping default values for nested named parameters

https://docs.raku.org/type/Signature

Why does constraining a Perl 6 named parameter to a definite value make it a required value?

Upvotes: 4

Views: 115

Answers (2)

raiph
raiph

Reputation: 32414

TL;DR For most use cases I'd go with @codesections' answer. But someone might appreciate this approach, which uses wrap.

Leave the existing some_func definition completely as is. Then add code like this:

&some_func .wrap: sub (|args) {
    if args<vel acceleration> .all { func1 |args.list }
    else { nextsame }
}

Job done.

Discussion

How can I check within the function body that these two named parameters are being passed?

You can do pretty much anything you want to do if you're willing to abuse Raku's slang capabilities to change the language.

But short of that, as far as I know you either need to change &some-func's definition, or leave it unchanged. @codesections showed some ways to change it. Here I show new code you can add to produce a similar effect:

Add this code:

&some_func .wrap: sub (|args) {
    if args<vel acceleration> .all { func1 |args.list }
    else { nextsame }
}

(See wrap, |args, <foo bar>, .all, .list, nextsame for more info.)

Now you can call it like this:

some_func 42, 99;
some_func 42, 99, vel => 1;
some_func 42, 99, acceleration => 2;
some_func 42, 99, vel => 3, acceleration => 4;

And it'll work as you specified.

Upvotes: 1

codesections
codesections

Reputation: 9600

There are multiple ways to achieve this, though – as far as I know – all of them involve changing &some-func's definition (not just its body).

I'd probably do it like this: split the function into a multi with one candidate that handles the case where both arguments are passed. You can do that by marking the named arguments as required for that candidate a !. Here's how that would look:

multi some-func3($x, $y, :$vel!, :$acceleration!) {
        note "Both arguments passed"
}

multi some-func3($x, $y, :$vel = 1, :$acceleration = 1 ÷ $vel) {
        note "Do something else"
}

Or you could do it by dropping the default values from the function signature and then setting them in the body (this solution will likely look familiar to anyone used to a programming language that doesn't support default values). Note that modifying $vel and $acceleration requires marking them as is copy.

sub some-func($x, $y, :$vel is copy, :$acceleration is copy) {
    if $vel.defined && $acceleration.defined {
        note "Both not missing"
    } else {
        $vel //= 1; 
        $acceleration //= 1 ÷ $vel;
        note "Do something else"
    }
}

Another approach would be to capture all of the arguments to some-func before assigning defaults and then to inspect the Capture. That might look like:

sub some-func2(|c ($x, $y, :$vel = 1, :$acceleration = 1 ÷ $vel)) {
    when c<vel> & c<acceleration> ~~ Any:D { note "Both args passed" }
    note "Do something else"
}

(Note that this approach gives slightly worse error messages when some-func is passed the wrong arguments)

Or you could give each default value a marker role that you can test against in the function body:

my role MyDefault {}
sub some-func5($x, $y, :$vel = 1 but MyDefault,
                       :$acceleration = (1 ÷ $vel) but MyDefault) {
    when $vel | $acceleration ~~ MyDefault { note "Do something else"}
    note "Both args passed"
}

Or, a slight variant of the above: use a string literal to auto-generate a role rather than defining one manually. This is slightly less typesafe – it doesn't protect against typos in the role name – but is also a bit more concise.

sub some-func($x, $y, :$vel = 1 but 'default',
                      :$acceleration = (1 ÷ $vel) but 'default') {
    when $vel | $acceleration eq 'default' { note "Do something else"}
    note "Both args passed"
}

Like I said, I'd go with the multi. But I hope that seeing several different ways helps you think about the problem – and maybe even teaches a bit of more advanced Raku syntax.

Upvotes: 4

Related Questions