Search code examples
rakurakudo

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?


Solution

  • 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.