Reputation: 3853
I'm following the Real World Haskell book. In the chapter about Monads, they give a simple example using the list monad to compute all pairs of numbers (x, y) that such that x * y == n
.
Their solution is:
multiplyTo :: Int -> [(Int, Int)]
multiplyTo n = do
x <- [1..n]
y <- [x..n]
guarded (x * y == n) $
return (x, y)
guarded :: Bool -> [a] -> [a]
guarded True xs = xs
guarded False _ = []
But I was wondering if I could restate guarded
for any monad.
Since fail
in the list monad is fail _ = []
, I though I could do:
guarded :: (Monad m) => Bool -> m a -> m a
guarded True = id
guarded False = fail "skipped"
However, this actually fails in ghci:
*Main> multiplyTo 24
*** Exception: skipped
I had a hunch which I cannot fully explain. These two version work:
guarded :: (Monad m) => Bool -> m a -> m a
guarded True = id
guarded False = \s -> fail "skipped"
guarded :: (Monad m) => Bool -> m a -> m a
guarded True xs = xs
guarded False _ = fail "skipped"
The type of fail "skipped"
is Monad m => m a
, whereas the type of guarded False
is Monad m => m a -> m a
. Then how is it possible that my first definition of guarded
type-checks?
Upvotes: 3
Views: 86
Reputation: 120741
You're being tripped up by the controversial function monad instance (actually this is not that controversial in the Haskell community, but I personally think we might have been better off if it didn't exist) together with the uncontroversially broken fail
method.
Look at the types:
guarded False
= fail "skipped" :: m a -> m a
≡ (fail :: String -> (m a -> m a)) "skipped"
≡ (fail :: String -> F (m a)) "skipped" -- with `type F x = m a -> x`
I.e., you calling fail
on the (->) (m a)
monad, and that does not define a custom fail
implementation, so it defaults to the error one
fail :: String -> ((->) r) a
fail s = errorWithoutStackTrace s
Note how this even typechecks if you remove the Monad m
constraint from your function, because the fail
doesn't use that monad.
The correct generalisation of your function is
guarded :: Alternative f => Bool -> f a -> f a
guarded True = id
guarded False = const empty
This does not typecheck if I erronously forget the const
, because functions are not an instance of Alternative
.
Upvotes: 7