#!/usr/bin/env perl
# Copyright (c) 2015-2020 Christian Jaeger, copying@christianjaeger.ch
# This is free software. See the file COPYING.md that came bundled
# with this file.
use
strict;
use
warnings;
# find modules from functional-perl working directory (not installed)
our
(
$mydir
,
$myname
);
BEGIN {
my
$location
= (-l $0) ? abs_path($0) : $0;
$location
=~ /(.*?)([^\/]+?)_?\z/s or
die
"?"
;
(
$mydir
,
$myname
) = ($1, $2);
}
use
FP::BigInt;
use
Chj::Backtrace;
# fibs :: [Integer]
# fibs = 1:1:zipWith (+) fibs (tail fibs)
# fib n = fibs!!n
our
$fibs
;
$fibs
= cons bigint(1), cons bigint(1),
lazy { stream_zip_with \
&add
, Keep(
$fibs
), rest
$fibs
};
sub
fib {
my
(
$n
) =
@_
;
stream_ref Keep(
$fibs
),
$n
}
# The above code creates the sequence only once per program run and
# then keeps it around in the $fibs global; there's no provision to
# set $fibs again thus it has to be protected with Keep() from being
# deleted. While this works, and is arguably efficient since multiple
# calls to fib() do not need to recalculate the stream, it also means
# that the whole stream up to the highest $n ever calculated are kept
# in memory until program exit.
# Here is an alternative definition that doesn't keep the stream tied
# to a global, but instead returns a fresh copy each time fibs() is
# called:
sub
fibs {
my
$fibs
;
$fibs
= cons bigint(1), cons bigint(1),
lazy { stream_zip_with \
&add
,
$fibs
, rest
$fibs
};
$fibs
}
# Note that while it creates a reference cycle, it won't leak, as the
# cycle is broken by stream_zip_with weakening its arguments, which we
# don't protect here (we do not use a Keep() wrapper).
# Here's a variant that relies on self-referencing the subroutine (a
# package variable) instead of mutating a lexical variable:
sub
fibs2 {
cons bigint(1), cons bigint(1), lazy {
my
$fibs
= fibs2();
stream_zip_with \
&add
,
$fibs
, rest
$fibs
}
}
# But it's less efficient: it recalculates parts multiple times, as
# can be seen with CPU timings, or with the number of times that it
# calls \&add; you can check for the latter by replacing \&add with
# \&counting_add and look at $addcount before and after the
# calculation:
my
$addcount
= 0;
sub
counting_add {
$addcount
++;
$_
[0] +
$_
[1]
}
TEST {
$addcount
= 0;
# local *add = \&counting_add;
# to avoid redefinition warnings, use the glob (alternatively
# disable the warning):
local
*add
=
*counting_add
;
[fibs->
ref
(80),
$addcount
]
}
[bigint(
'37889062373143906'
), 79];
TEST {
$addcount
= 0;
local
*add
=
*counting_add
;
[fibs2->
ref
(80),
$addcount
]
}
[bigint(
'37889062373143906'
), 3160];
# 3160 == 79*80/2
# This is because the recursive call to `fibs2` is creating a second,
# independently calculated sequence, which itself at the second
# position again is creating another (third), independently calculated
# sequence, etc. We want to calculate the same sequence only once,
# which `fibs` achieves.
TEST { Keep(
$fibs
)->take(10)->
map
(\
&stringify
)->array }
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
TEST { fib 30 }
bigint(1346269);
TEST { fibs->
ref
(30) }
bigint(1346269);
TEST { fibs2->
ref
(30) }
bigint(1346269);
# ------------------------------------------------------------------
# Alright, so there are not only the stupidly slow naive recursive and
# the above widespread O(n) algorithm (as well as the fibs2 variant
# which is inbetween), but also better ones:
# Thus if we wanted something really fast, we'd diverge from standard
# examples and instead implement the following:
# (we're adding this for completeness and some perspective on the
# topic of performance, not as a demo of functional-perl)
sub
_fib {
my
(
$n
) =
@_
;
(
$n
== 0) ? (bigint(0), bigint(1)) :
do
{
no
warnings
'recursion'
;
my
(
$a
,
$b
) = _fib(
$n
/ 2);
my
$c
=
$a
* (
$b
* 2 -
$a
);
my
$d
=
$a
*
$a
+
$b
*
$b
;
(
$n
% 2) == 0 ? (
$c
,
$d
) : (
$d
,
$c
+
$d
)
};
}
sub
_fibonacci {
my
(
$n
) =
@_
;
(
$n
>= 0) ? (_fib
$n
)[0] :
die
"n < 0"
;
}
sub
fibonacci {
my
(
$n
) =
@_
;
_fibonacci(
$n
+ 1)
}
TEST { fibonacci 30 }
bigint(1346269);
# With bigger inputs:
use
Chj::time_this;
my
$a
= lazy {
time_this { fib(1400) }
"fib"
};
my
$b
= lazy {
time_this { fibonacci(1400) }
"fibonacci"
};
TEST { FORCE(
$a
) .
""
}
"27682097123729003105677626449505746191732174241149462650923690069660131404640833946850969379619575819465246124164576690629246144675379393573106211382631761722558988596174542778374560842981861789646519389087106627252306193512024601313327314953992743800841043870569935864482663072626507327493026"
;
TEST { FORCE(
$b
) }
$a
;
perhaps_run_tests
"main"
or
do
{
FP::Repl::repl();
};