functional-programmingrakupurely-functionalrakudo

`is pure` trait and default parameters


The following &greet function is pure, and can appropriately be marked with the is pure trait.

sub greet(Str:D $name) { say "Hello, $name" }
my $user = get-from-db('USER');
greet($user);

This one, however, is not:

sub greet { 
    my $name = get-from-db('USER');
    say "Hello, $name" 
}

greet($user);

What about this one, though?

sub greet(Str:D $name = get-from-db('USER')) { say "Hello, $name" }

greet();

From "inside" the function, it seems pure – when is parameters are bound to the same values, it always produces the same output, without side effects. But from outside the function, it seems impure – when called twice with the same argument, it can produce different return values. Which prospective does Raku/Rakudo take?


Solution

  • There are at least two strategies a language might take when implementing default values for parameters:

    1. Treat the parameter default value as something that the compiler, upon encountering a call without enough arguments, should emit at the callsite in order to produce the extra argument to pass to the callee. This means that it's possible to support default values for parameters without any explicit support for it in the calling conventions. This also, however, requires that you always know where the call is going at compile time (or at least know it accurately enough to insert the default value, and one can't expect to use different default values in method overrides in subclasses and have it work out).
    2. Have a calling convention powerful enough that the callee can discover that a value was not passed for the parameter, and then compute the default value.

    With its dynamic nature, only the second of these really makes sense for Raku, and so that is what it does.

    In a language doing strategy 1 it could arguably make sense to mark such a function as pure, insofar as the code that calculates the default lives at each callsite, and so anything doing an analysis and perhaps transformation based upon the purity will already be having to deal with the code that evaluates the default value, and can see that it is not a source of a pure value.

    Under strategy 2, and thus Raku, we should understand default values as an implementation detail of the block or routine that has the default in its signature. Thus if the code calculating the default value is impure, then the routine as a whole is impure, and so the is pure trait is not suitable.

    More generally, the is pure trait is applicable if for a given argument capture we can always expect the same return value. In the example given, the argument capture \() contradicts this.

    An alternative factoring here would be to use multi subs instead of parameter defaults, and to mark only one candidate with is pure.