10
votes

One irritation with lazy IO caught to my attention recently

import System.IO
import Control.Applicative

main = withFile "test.txt" ReadMode getLines >>= mapM_ putStrLn
  where getLines h = lines <$> hGetContents h

Due to lazy IO, the above program prints nothing. So I imagined this could be solved with a strict version of fmap. And indeed, I did come up with just such a combinator:

forceM :: Monad m => m a -> m a
forceM m = do v <- m; return $! v

(<$!>) :: Monad m => (a -> b) -> m a -> m b
f <$!> m = liftM f (forceM m)

Replacing <$> with <$!> does indeed alleviate the problem. However, I am not satisfied. <$!> has a Monad constraint, which feels too tight; it's companion <$> requires only Functor.

Is there a way to write <$!> without the Monad constraint? If so, how? If not, why not? I've tried throwing strictness all over the place, to no avail (following code does not work as desired):

forceF :: Functor f => f a -> f a
forceF m = fmap (\x -> seq x x) $! m

(<$!>) :: Functor f => (a -> b) -> f a -> f b
f <$!> m = fmap (f $!) $! (forceF $! m)
1
"(\x -> seq x x)" is precisely id, so that doesn't help.Daniel Fischer
This seems a very strange definition of <$!> to me. I'd go with "f <$!> m = forceM (liftM f m)" instead. This also solves your problem, and seems a lot more sensible in other contexts. I suspect you can't define this for functors, though.lpsmith

1 Answers

8
votes

I don't think it's possible, and also the monadic forceM doesn't work for all monads:

module Force where

import Control.Monad.State.Lazy

forceM :: Monad m => m a -> m a
forceM m = do v <- m; return $! v

(<$!>) :: Monad m => (a -> b) -> m a -> m b
f <$!> m = liftM f (forceM m)

test :: Int
test = evalState (const 1 <$!> undefined) True

And the evaluation:

Prelude Force> test
1

forceM needs a strict enough (>>=) to actually force the result of its argument. Functor doesn't even have a (>>=). I don't see how one could write an effective forceF. (That doesn't prove it's impossible, of course.)