manu
manu

Reputation: 3853

Why does `guarded False = fail "skipped"` type-checks?

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

Answers (1)

leftaroundabout
leftaroundabout

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

Related Questions