My two submissions for a programming problem differ in just one expression (where anchors
is a nonempty list and (getIntegrals n)
is a state monad):
Here we're looking both times at what corresponds to, in the first instance the inlined part and the second instance the actual call to replicateM.
readRest = replicateM (length anchors - 1) (getIntegrals n)
Now the interesting bit is that in the inlined code the yellow highlighted lines are run in every loop of replicateM
, while in the non-inlined part they are calculated once, outside the lambda abstraction which is passed to replicateM
.
But what are they doing? There are multiple variables called ds
in the core, but this one refers to this:
which in turn corresponds to
solve = do
n <- getIntegral
So what I think is happening is that instead of running getIntegral
once and saving the result, it's starting state is saved and it is rerun with this state for every pass of the loop. Indeed changing this line to the following (requires BangPatterns language extension) fixes all versions (submission).
solve = do
!n <- getIntegral
I'm still not really sure, but this is my best guess.
Here are the two core dumps for reference: Inline, Noinline
This is crazy
Well yes, but I feel that the underlying problem here is your use of lazy IO and lazy State. Using the strict State transformer Haskell probably would have figured out to not keep old state around (I have no idea, just a guess), however we can not use strict State here, because of your reliance on lazy IO, i.e. getting all the input at the beginning using getContents
and lazyly forcing it while making sure to provide output before forcing too much. Instead it would be a lot safer to explicitely read the input line by line. I.e. replace the StateT [ByteString]
with IO
or something more fancy like a Conduit
or Pipe
.