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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With