The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

#!/usr/bin/env perl
# Copyright (c) 2015-2019 Christian Jaeger, copying@christianjaeger.ch
# This is free software. See the file COPYING.md that came bundled
# with this file.
use strict;
use warnings FATAL => 'uninitialized';
# find modules from functional-perl working directory (not installed)
use Cwd 'abs_path';
our ($mydir, $myname);
BEGIN {
my $location = (-l $0) ? abs_path($0) : $0;
$location =~ /(.*?)([^\/]+?)_?\z/s or die "?";
($mydir, $myname) = ($1, $2);
}
use lib "$mydir/../lib";
# gracefully fail when run as a test and Method::Signatures is not
# available
use Chj::TEST use => 'Method::Signatures';
use FP::List ":all";
use FP::Ops ":all";
use FP::Lazy ":all";
use FP::Stream ":all";
# Another translation of (a) Haskell example(s) (see also `fibs`).
# ------------------------------------------------------------------
# Note that this is *not* an efficient algorithm to calculate primes;
# further down in this file.
# primes = sieve [2..]
# where sieve (p:xs) =
# p : sieve [x | x <- xs, x `mod` p /= 0]
func primes_naive1() {
my $sieve;
$sieve = func($p_xs) {
my ($p, $xs) = first_and_rest $p_xs;
lazy {
cons $p, &$sieve(
stream_filter func($x)
{
$x % $p != 0
},
$xs
)
}
};
# XXX Weakened($sieve)
$sieve->(stream_iota 2)
}
# or simpler, by lifting `sieve` to the toplevel:
func primes_naive() {
sieve(stream_iota 2)
}
func sieve($p_xs) {
my ($p, $xs) = first_and_rest $p_xs;
lazy {
cons $p, sieve(
stream_filter func($x)
{
$x % $p != 0
},
$xs
)
}
}
func t_primes($primes) {
TEST { stream_to_array stream_take &$primes(), 10 }
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29];
}
t_primes \&primes_naive1;
t_primes \&primes_naive;
# ------------------------------------------------------------------
# (B) Now let's use a better algorithm:
# merge :: (Ord a) => [a] -> [a] -> [a]
# merge xs@(x:xt) ys@(y:yt) =
# case compare x y of
# LT -> x : (merge xt ys)
# EQ -> x : (merge xt yt)
# GT -> y : (merge xs yt)
#
# diff :: (Ord a) => [a] -> [a] -> [a]
# diff xs@(x:xt) ys@(y:yt) =
# case compare x y of
# LT -> x : (diff xt ys)
# EQ -> diff xt yt
# GT -> diff xs yt
#
# primes, nonprimes :: [Integer]
# primes = [2, 3, 5] ++ (diff [7, 9 ..] nonprimes)
# nonprimes = foldr1 f $ map g $ tail primes
# where
# f (x:xt) ys = x : (merge xt ys)
# g p = [ n * p | n <- [p, p + 2 ..]]
func merge($xs, $ys) {
lazy {
my ($x, $xt) = first_and_rest $xs;
my ($y, $yt) = first_and_rest $ys;
if ($x < $y) {
cons($x, merge($xt, $ys))
} elsif ($x == $y) {
cons($x, merge($xt, $yt))
} else {
cons($y, merge($xs, $yt))
}
}
}
func diff($xs, $ys) {
lazy {
my ($x, $xt) = first_and_rest $xs;
my ($y, $yt) = first_and_rest $ys;
if ($x < $y) {
cons($x, diff($xt, $ys))
} elsif ($x == $y) {
diff($xt, $yt)
} else {
diff($xs, $yt)
}
}
}
func primes() {
stream_append(
array_to_list([2, 3, 5]),
diff(
stream_map(
func($x)
{
$x * 2 + 7
},
stream_iota
),
lazy { nonprimes() }
)
)
}
func nonprimes() {
my $f = func($x_xt, $ys) {
my ($x, $xt) = first_and_rest $x_xt;
cons $x, merge($xt, $ys)
};
my $g = func($p) {
stream_map(func($n) { $n * $p }, stream_map(func($q) { $p + $q * 2 },
stream_iota));
};
stream_foldr1 $f, stream_map $g, rest primes
}
# And tests...
TEST {
stream_to_array stream_take(
diff(
stream_iota(1),
stream_map func($x)
{
$x * 2
},
stream_iota(1)
),
10
)
}
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19];
TEST {
stream_to_array stream_take(
diff(
stream_iota(10),
stream_map func($x)
{
$x * 2
},
stream_iota(1)
),
10
)
}
[11, 13, 15, 17, 19, 21, 23, 25, 27, 29];
# merge:
func t_merge($a1, $a2, $n) {
stream_to_array stream_take(
merge(array_to_stream($a1), array_to_stream($a2),), $n);
}
TEST { t_merge [1, 2, 99], [3, 4, 99], 4 }
[1, 2, 3, 4];
TEST { t_merge [1, 3, 99], [3, 4, 99], 4 }
[1, 3, 4, 99]; # not [1,3,3,4]
TEST { t_merge [1, 3, 99], [2, 4, 99], 4 }
[1, 2, 3, 4];
# and the whole thing:
t_primes \&primes;
# Benchmarking:
# [[times], stream_ref (primes_naive, 1000), [times]] -> 8.86 sec user time
# also, the above prints many deep recursion warnings (why exactly?)
# [[times], stream_ref (primes, 1000), [times]] -> 0.78 sec user time
# [[times], stream_ref (primes, 10000), [times]] -> 17.46 sec user time
# (versus ghc compiled code which takes 0.1 sec for the same)
# ------------------------------------------------------------------
# for the test suite:
perhaps_run_tests "main" or do {
require FP::Repl;
FP::Repl::repl();
}