Memoization
Consider this famously slow implementation of the Fibonacci function, in J:
fib =: monad define"0 M.
if. y = 0 do.
1
elseif. y = 1 do.
1
elseif. do.
(fib y - 1) + fib y - 2
end.
)
echo fib 40
Without the M. that auto-memoizes the function, the last line takes about a minute. The BQN equivalent - without memoization - only takes 13s on CBQN, but that's still very slow compared to the near-instant memoized call.
BQN doesn't have M. but it does have lexical scope, mutable hashmaps, and 1-modifiers:
_memo β {f _π£: mββ’HashMapΛβ¨β© β {m.Has π¨βπ©? m.Get π¨βπ©; (π¨βπ©)βΈm.SetβΈβ’ π¨Fπ©}}
Fib β {
0: 1;
1: 1;
π: (Fib π© - 1) + Fib π© - 2
}β0 _memo
β’Show Fib 40
Some notes:
- Direct recursion (
$:in J, π in BQN) bypasses the cache in both languages. - This one-line feature comes with many advantages over J's primitive, as you can control the cache, clear it, prevent it from getting too large, serialize it to disk, use a derived key rather than the exact arguments - and can memoize more than just atoms.
- Manual memoization is also possible in J - Block Jank has an example of closure-like J.
Lil doesn't have these late-bound recursive calls, so a separate memofib doesn't seem possible. Dicts also aren't exactly mutable hashtables, but still work well enough for this example:
m:() on memofib x do
if !m[x] m[x]:
if x~0 1 elseif x~1 1 else
memofib[x-1]+memofib[x-2]
end
end
m[x]
end