为什么 `guarded False = fail "skipped"` 类型检查?

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

我正在关注真实世界 Haskell 这本书。在关于 Monad 的章节中,他们给出了一个简单的例子,使用列表 monad 来计算所有满足 x * y == n.

的数字对 (x, y)

他们的解决方案是:

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 _ = []

但我想知道是否可以为任何 monad 重述 guarded

因为列表 monad 中的 failfail _ = [],我认为我可以这样做:

guarded :: (Monad m) => Bool -> m a -> m a
guarded True = id
guarded False = fail "skipped"

然而,这实际上在 ghci 中失败了:

*Main> multiplyTo 24
*** Exception: skipped

我有一种无法完全解释的预感。这两个版本工作:

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"

fail "skipped"的类型是Monad m => m a,而guarded False的类型是Monad m => m a -> m a。那么我对 guarded 的第一个定义怎么可能进行类型检查?

你被有争议的 function monad 实例 绊倒了(实际上这在 Haskell 社区中并没有那么有争议,但我个人认为我们可能有如果它不存在会更好)和无可争议的破坏 fail 方法。

看类型:

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`

即,您在 (->) (m a) monad 上调用 fail,然后 does not define a custom fail implementation, so it defaults to the error one

  fail        :: String -> ((->) r) a
  fail s      = errorWithoutStackTrace s

请注意,如果您从函数中删除 Monad m 约束,这甚至会如何进行类型检查,因为 fail 不使用那个 monad.

你的函数的正确概括是

guarded :: Alternative f => Bool -> f a -> f a
guarded True = id
guarded False = const empty

如果我错误地忘记了 const,这 不会 进行类型检查,因为函数不是 Alternative.

的实例