Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I force bindings in a where clause to be CAFs?

Tags:

haskell

I have some bindings which I want to keep private to a function (so, not declared in the top-level module namespace), but which are expensive to compute so I'd prefer them to be CAFs. Is there a pragma or some other trick I could use to force GHC to allocate a CAF for those bindings?

example code:

header :: HeaderProps -> Element
header = div_ [style_ headerStyle] [str_ "Here be headerz"]
  where
    -- Creating the 'headerStyle' is expensive! Do it only once
    -- regardless of how many times the 'header' function is used.
    headerStyle = mkStyle $ do
        display flex
        flexDirection column
        padding (px 20) (px 40)

If that is not possible, I see a few options, but each has its own drawbacks:

  1. Move each function into its own module so I can move the expensive bindings to top-level module namespace without having to worry about other functions accessing them. Since I expect there to be many such functions, the number of modules would explode (hundreds in a large project). And I'm not a fan of the only a single function per module religion like it is preached in the node.js community.
  2. Move the bindings to the top-level module namespace and give each a sufficiently unique name to avoid conflicts and make it abundantly clear that those are private (eg. headerStyle -> header__headerStyle).
  3. Use TemplateHaskell to offload the expensive computations to compile-time.
like image 988
wereHamster Avatar asked Dec 30 '16 19:12

wereHamster


1 Answers

If I've understood your intent correct, this is actually easy: just move any function arguments into a lambda, so the entire function (including the where block) is a CAF:

foo :: Int -> Int
foo = \x -> x * nFermat
 where nFermat = length [() | a<-[1..m], b<-[1..m], c<-[1..m], a^3+b^3==c^3]
       m = 200

main = interact $ show . foos . read
 where foos n = foo <$> [0..n]

This way, whatever x arguments you use, the same nFermat will be reused.

sagemuej@sagemuej-X302LA:/tmp$ time runhaskell fermata.hs <<< 1
[0,0]
real    0m23.199s
user    0m23.177s
sys 0m0.045s
sagemuej@sagemuej-X302LA:/tmp$ time runhaskell fermata.hs <<< 100
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
real    0m22.629s
user    0m22.601s
sys 0m0.052s
like image 53
leftaroundabout Avatar answered Oct 16 '22 00:10

leftaroundabout