FunctionalPerl
view release on metacpan or search on metacpan
docs/intro.md view on Meta::CPAN
Let's try a lexical variable instead (`my $a`):
fperl> our ($f1,$f2) = do { my $a = 10; my $f1 = sub { $a }; $a = 11; my $f2 = sub { $a }; ($f1,$f2) }
$VAR1 = sub { "DUMMY" };
$VAR2 = sub { "DUMMY" };
fperl> &$f1
$VAR1 = 11;
Still the same result: the two subroutines are still referring to the
same instance of a variable. Since `$a` only lives lexically in the do
block though, the subroutines now need to store a pointer reference to
it (the way this is implemented is by storing both a pointer to the
compiled code, and a pointer to the variable together in the CODE ref
data structure).
Now let's use a fresh lexical variable for the second value (11)
instead:
fperl> our ($f1,$f2) = do { my $a = 10; my $f1 = sub { $a }; { my $a = 11; my $f2 = sub { $a }; ($f1,$f2) }}
$VAR1 = sub { "DUMMY" };
$VAR2 = sub { "DUMMY" };
fperl> &$f1
$VAR1 = 10;
fperl> &$f2
$VAR1 = 11;
This way we didn't change what `$f1`, including its indirect
references, refers to. Thus `$f1` remained a pure function here: it
follows the rule that pure functions *only* depend on the values they
receive as their arguments, and don't do *anything* visibly to the
rest of the program other than giving a result value. Just as with the
lists above, this is a good property to have, as it makes a value (be
it a function, or another value like a list) reliable. A pure function
or purely functional value does not carry a risk of giving different
behaviour at different times.
So, in conclusion, the safe and purely functional way is to only ever
use fresh variable instances, i.e. initialize them when introduced and
never modifying them afterwards. You might find this odd, a variable
is supposed to vary, no? But notice that each function call (even of
the same function) opens a new scope, and the variables introduced in
it are hence fresh instances every time it is called:
fperl> sub f ($x) { fun ($y) { [$x,$y] }}
fperl> our $f1 = f(12); our $f2 = f(14); &$f1("f1")
$VAR1 = [12, 'f1'];
fperl> &$f2("f2")
$VAR1 = [14, 'f2'];
You can see that a new instance of `$x` is introduced for every call
to `f`.
You may be thinking that there's no way around mutating variables:
loops can't introduce new variable instances on every loop iteration:
you'd have to put the variable declaration inside the loop and then it
would lose its value across the loop iteration. Well--it's true that
you can't do that with the loop syntax that Perl offers (`for`,
`while`). But you are not forced to use those. Iteration just means to
process work step by step, i.e. do a step of work, check whether the
work is finished, and if it isn't, get the next piece of work and
start over. You can easily formulate this with a function that takes
the relevant pieces of information (remainder of the work, accumulated
result), checks to see if the work is done and if it isn't, calls
itself with the remainder and new result.
fperl> sub build ($i,$l) { if ($i > 0) { build($i-1, cons fun () { $i }, $l) } else { $l }}
fperl> build(3, null)
$VAR1 = list(sub { "DUMMY" }, sub { "DUMMY" }, sub { "DUMMY" });
This uses a new instance of `$i` in each iteration, as you can see
from this:
fperl> $VAR1->map(fun ($v) { &$v() })
$VAR1 = list(1, 2, 3);
There's one potential problem with this, though, which is that perl
allocates a new frame on the call stack for every nested call to
`build`, which means it needs memory proportional to the number of
iterations. But perl also offers a solution for this:
fperl> sub build ($i,$l) { if ($i > 0) { @_ = ($i-1, cons fun () { $i }, $l); goto &build } else { $l }}
Sorry for the one-line formatting here, our examples are starting to
get a bit long for the repl, here is the same with line breaks:
sub build ($i,$l) {
if ($i > 0) {
@_ = ($i-1, cons fun () { $i }, $l);
goto &build
} else {
$l
}
}
That still looks pretty ugly, though. But there's also a solution for
*that*: if you install `Sub::Call::Tail` (`fperl` automatically loads
it on start), then you can instead simply prepend the `tail` keyword
to the recursive function call to achieve the same:
fperl> sub build ($i,$l) { if ($i > 0) { tail build($i-1, cons fun () { $i }, $l) } else { $l }}
i.e.
sub build ($i,$l) {
if ($i > 0) {
tail build($i-1, cons fun () { $i }, $l)
} else {
$l
}
}
What `tail` (or the written-out `goto` variant above) means is "this
function call is the last operation in the current program flow of
this function (it is in tail position); don't allocate a new stack
frame for it". (It might be useful to try to write a perl module that
automatically does the tail call recognition in its scope.)
## The REPL revisited
( run in 2.800 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )