How do I make a Constant Applicative Form into, well, not a Constant Applicative Form, to stop it being retained for the lifetime of the program?
I\'ve tried this ap
You need to hide the fact that the rhs is a CAF from the optimizer. Something like this should do it.
twoTrues :: () -> [[[Bool]]]
twoTrues u = map (++ (True : repeat (false u))) . trueBlock <$> [1..]
{-# NOINLINE false #-}
false :: () -> Bool
false _ = False
The simplest possible solution might be to tell the compiler to inline it. (Note: this answer is untested. If it doesn't work, please comment below.)
Even if (hypothetically) the compiler refuses to inline it for some reason, you can use cpp
instead, by #defining it:
#define twoTrues (map (++ (True : repeat False)) . trueBlock <$> [1..])
(although with that approach, of course, it won't appear in the module's interface, so you can only use it within that module).
The -cpp
option tells GHC to preprocess the input file with cpp.
Inlining would mean duplicating the code n times if you refer to twoTrues
in n>1 places. However, while that's theoretically undesirable, it's probably not going to be a significant problem in practice.
It seems to be a long-standing problem http://hackage.haskell.org/trac/ghc/ticket/917 . And in my opinion a critical one.
With the introduction of a dummy parameter, you also have to make it look like the result actually depends on the parameter. Otherwise, GHC's cleverness might turn it into a CAF again.
I suggest the following:
twoTrues u = map (++ (True : repeat False)) . trueBlock <$> [(u `seq` 1)..]
Generalise. If you have a constant value, can you generalise this to a function of some variable? The naming of my function in the question, twoTrues
, immediately suggests that this constant is the third in a sequence zeroTrues
, oneTrue
, twoTrues
, threeTrues
etc. - and indeed it is. So generalising twoTrues
into a function nTrues
which takes a parameter n and deleting twoTrues
, would eliminate one CAF from the program.
As it happens, in this case, I had only considered the cases zeroTrues
, oneTrue
and twoTrues
for my program because that was all I needed, but my program could naturally be extended to deal with nTrues
for n
> 2 - so generalising to nTrues
would mean it would make sense to "generalise all the way up" to the users of zeroTrues
, oneTrue
etc. That would not always be the case.
Note: there might still be other CAFs to deal with, either in the code, or produced by GHC's "optimisations" (which are not really optimisations in these pathological cases).
This answer may involve more work by the programmer than is strictly necessary, however. It isn't actually necessary to generalise, as Don's answer shows.
On the other hand, in some cases, generalising a constant can make it more clear what you are actually doing, and aid reusability. It can even reveal ways to compute a series of values in a better systematic way, and/or more efficiently.
A note about this particular case (which can be ignored): In this particular case, I would not want to make nTrues
itself into an infinite list (which would be a CAF again, reintroducing the original problem!) rather than a function. One reason is that while twoTrues
could be useful in the form of an infinite list, I can't see how it would be useful (in my application, anyway) for nTrues
to be in the form of an infinite list.
A complete example
Here's a little example that shows the situation:
module A where
big :: () -> [Int]
big _ = [1..10^7]
Looks like a function, right? But what does GHC do? It floats the enum to the top level!
A.big1 :: [Int]
[ Unf=Unf{Src=<vanilla>, TopLvl=True, Arity=0, Value=False,
ConLike=False, Cheap=False, Expandable=False,
Guidance=IF_ARGS [] 7 0}]
A.big1 =
case A.$wf1 10 A.big2 of ww_sDD { __DEFAULT ->
eftInt 1 ww_sDD
}
A.big :: () -> [Int]
[Arity=1,
Unf=Unf{Src=InlineStable, TopLvl=True, Arity=1, Value=True,
ConLike=True, Cheap=True, Expandable=True,
Guidance=ALWAYS_IF(unsat_ok=True,boring_ok=True)
Tmpl= \ _ -> A.big1}]
A.big = \ _ -> A.big1
Ooops!
So what can we do?
Turn off optimizations
That works, -Onot
, but not desirable:
A.big :: () -> [Int]
[GblId, Arity=1]
A.big =
\ _ ->
enumFromTo
@ Int
$fEnumInt
(I# 1)
(^
@ Int
@ Type.Integer
$fNumInt
$fIntegralInteger
(I# 10)
(smallInteger 7))
Don't inline, and more functons
Make everything into a function, including the enumFromTo
, plumbing the parameter through to the workers:
big :: () -> [Int]
big u = myEnumFromTo u 1 (10^7)
{-# NOINLINE big #-}
myEnumFromTo :: () -> Int -> Int -> [Int]
myEnumFromTo _ n m = enumFromTo n m
{-# NOINLINE myEnumFromTo #-}
Now we are finally CAF-free! Even with -O2
A.myEnumFromTo [InlPrag=NOINLINE]
:: () -> Int -> Int -> [Int]
A.myEnumFromTo =
\ _ (n_afx :: Int) (m_afy :: Int) ->
$fEnumInt_$cenumFromTo n_afx m_afy
A.big [InlPrag=NOINLINE] :: () -> [Int]
A.big = \ (u_abx :: ()) -> A.myEnumFromTo u_abx A.$s^2 lvl3_rEe
Yay.
What doesn't work?
Turn off -ffull-laziness
The full laziness transformation floats definitions outwards. It is on by default with -O1
or above. Let's try turning it off with -fno-full-laziness
. However, it doesn't work.