Polymorphic \"constants\", like 5 :: Num a => a, aren\'t really constants but functions of a dictionary argument. Hence, if you define
primes ::
If you enable ConstraintKinds and ExistentialQuantification (or GADTs) you can reify type class dictionaries:
{-# LANGUAGE ConstraintKinds, ExistentialQuantification #-}
data Dict a = a => Dict
If we try this out
fibs :: Num n => [n]
fibs = 1 : 1 : zipWith (+) fibs (drop 1 fibs)
fibs' :: [Integer]
fibs' = fibs
fibsWithDict :: Dict (Num n) -> [n]
fibsWithDict Dict = fs
where
fs = 1 : 1 : zipWith (+) fs (drop 1 fs)
fibs'' :: [Integer]
fibs'' = fibsWithDict Dict
in GHCi we see
λ> :set +s
λ>
λ> fibs !! 29
832040
(2.66 secs, 721235304 bytes)
λ>
λ> fibs !! 29
832040
(2.52 secs, 714054736 bytes)
λ>
λ>
λ> fibs' !! 29
832040
(2.67 secs, 713510568 bytes)
λ>
λ> fibs' !! 29
832040
(0.00 secs, 1034296 bytes)
λ>
λ>
λ> fibs'' !! 29
832040
(0.00 secs, 1032624 bytes)
So fibs'' is the only implementation of the three that immediately memoizes.
Note that we have to pattern match on the Dict constructor. Otherwise, we will get an error about n not being constrained to have a Num instance (like you would expect if our signature was just fibsWithDict :: a -> [n]).
This is a full solution since you can consider fibsWithDict Dict to be an expression that memoizes immediately at any type you throw at it (as long as it's an instance of Num). For example:
λ> (fibsWithDict Dict !! 29) :: Double
832040.0
(0.00 secs, 1028384 bytes)
EDIT: It looks like this explicit dictionary passing isn't necessary here and can be done implicitly by using ScopedTypeVariables with a local binding:
{-# LANGUAGE ScopedTypeVariables #-}
fibsImplicitDict :: forall a. Num a => [a]
fibsImplicitDict
= let fs :: [a]
fs = 1 : 1 : zipWith (+) fs (drop 1 fs)
in
fs
(Thanks to bennofs for the insight here!)