Reputation: 3434
I decide to cleanup some of my code by using Reader
instance (->) r
to avoid passing some values constantly.
After playing around a bit, I found the following difference in behavior:
let p x y = print (x,y)
f = ($ (1 :: Int))
f $ do
p <$> (+ 2) <*> (+ 5)
p <$> (* 6) <*> (* 2)
-- prints (6,2) but not (3,6)
do
f $ p <$> (+ 2) <*> (+ 5)
f $ p <$> (* 6) <*> (* 2)
-- prints both (6,2) and (3,6)
After manually desugaring this and putting some type annotations:
let p x y = print (x,y)
f = ($ (1 :: Int))
f $ ((>>) :: forall m a. m ~ (Int -> a) => m -> m -> m)
(p <$> (+ 2) <*> (+ 5) :: Int -> IO ())
(p <$> (* 6) <*> (* 2))
((>>) :: forall m. m ~ IO () => m -> m -> m)
(f (p <$> (+ 2) <*> (+ 5) :: Int -> IO ()))
(f (p <$> (* 6) <*> (* 2)))
Now I suspect the problem is that while >>
for IO instance does do what I want, >>
for (-> r)
is too lazy to look into its left hand side.
So my question is, is there a way (perhaps a variant of >>
strict on LHS?), to do things like this without running in to this laziness problem?
Not sure ReaderT
would help in this case, but I think that might end up making the code more complicated so I haven't tried that.
Upvotes: 1
Views: 63
Reputation: 92057
Being strict on the LHS wouldn't matter at all: merely looking at an IO action does not perform it. It has to be combined with another IO action (via >>
or the like) and eventually end up as part of main
(or an action given to GHCI).
That is, as you already seem to realize, in the first case you have two (Int -> IO ())
values, which you combine using (>>)
from the Reader monad, which results in returning only the second IO action. In the second case you have two IO ()
actions, which you combine using (>>)
from the IO monad. Of course this combines the two into a single action.
This is simply a consequence of how IO actions are combined, namely, that you must combine them explicitly: it is not enough to merely compute them and then throw them away. What you are asking for is similar to asking two numbers which you compute in the Reader monad to be implicitly added together:
f :: Int -> Int -> Int
f x = (* x)
foo :: Int -> Int
foo = do
f 3
f 5
bar :: Int
bar = foo 4
First you compute 4*3, which is of course 12, and then you throw it away and compute 4*5, which is of course 20, and therefore the value bar is 20. The 12 is thrown away because you explicitly said not to do anything with it: it would be an error to add them together and let bar=32, or multiply them and let bar=240, or any such thing. Likewise, it would be incorrect for the IO ()
values inside your reader computation to be implicitly combined.
Upvotes: 3