Is `Monad` constraint necessary in `<$!>`

Viewed 123

As claimed in the documentation <$!> is the strict version of <$>, but surprisingly

<$!> :: Monad m => (a -> b) -> m a -> m b 
f <$!> m = do
  x <- m
  let z = f x
  z `seq` return z

instead of the more natural (in my opinion; because it keeps the weaker constraint and mimics $!)

<$!> :: Functor f => (a -> b) -> f a -> f b
f <$!> x = x `seq` (f <$> x)

I guess that appliying seq after the binding is different than the "natural" approach, but I don't know how different it is. My question is: Is there any reason which makes the "natural" approach useless, and that's why the implementation is constraint to Monad?

2 Answers

GHC's commit message includes the following two links which sheds more light on this function:

This was the reason which is mentioned by Johan Tibell for it (quoting from the linked mailing list):

It works on Monads instead of Functors as required by us inspecting the argument.

This version is highly convenient if you want to work with functors/applicatives in e.g. parser and avoid spurious thunks at the same time. I realized that it was needed while fixing large space usage (but not space-leak) issues in cassava.

I guess that appliying seq after the binding is different than the "natural" approach, but I don't know how different it is

Since haskell is functional, seq must work through data dependencies; it sets up a relationship: "when seq x y is evaluated to WHNF, a will have been as well".

The idea here is to pin the evaluation of a to the outer m a which we know must be evaluated for each >>= or <*> to proceed.

In your version:

Prelude> f <$!> x = x `seq` (f <$> x)
Prelude> let thunk = error "explode"
Prelude> case (+) <$!> Just thunk <*> Just thunk of ; Just _ -> "we can easily build up thunks"
"we can easily build up thunks"

I do wonder if there's a better solution possible though

Related