I have a tricky question;
So, I know that GHC will ‘cache’ (for lack of a better term) top level definitions and only compute them once, e.g. :
myList :: [Int]
myList = fmap (*10) [0..10]
Even if I use myList
in several spots, GHC notices the value has no params, so it can share it and won’t ‘rebuild’ the list.
I want to do that, but with a computation which depends on a type-level context; a simplified example is:
dependentList :: forall n. (KnownNat n) => [Nat]
dependentList = [0..natVal (Proxy @n)]
So the interesting thing here, is that there isn’t a ‘single’ cacheable value for dependentList
; but once a type is applied it reduces down to a constant, so in theory once the type-checker runs, GHC could recognize that several spots all depend on the ‘same’ dependentList
; e.g. (using TypeApplications)
main = do
print (dependentList @5)
print (dependentList @10)
print (dependentList @5)
My question is, will GHC recognize that it can share both of the 5
lists? Or does it compute each one separately? Technically it would even be possible to compute those values at Compile-time rather than run-time, is it possible to get GHC to do that?
My case is a little more complicated, but should follow the same constraints as the example, however my dependentList
-like value is intensive to compute.
I’m not at all opposed to doing this using a typeclass if it makes things possible; does GHC cache and re-use typeclass dictionaries? Maybe I could bake it into a constant in the typeclass dict to get caching?
Ideas anyone? Or anyone have reading for me to look at for how this works?
I'd prefer to do this in such a way that the compiler can figure it out rather than using manual memoization, but I'm open to ideas :)
Thanks for your time!