6 回答
我认为这是因为您需要指定要丢弃的值的类型。在 Haskell-98 中,()
是显而易见的选择。只要您知道类型是()
,您也可以制作该值()
(假设评估进行到那一步),以防万一有人试图对其进行模式匹配或其他事情。我认为大多数程序员不喜欢在代码中引入额外的⊥,因为这只是一个额外的陷阱。我当然避免它。
代替()
,可以创建一个无人居住的类型(当然除了 ⊥ )。
{-# LANGUAGE EmptyDataDecls #-}
data Void
mapM_ :: (Monad m) => (a -> m b) -> [a] -> m Void
现在甚至不可能进行模式匹配,因为没有Void
构造函数。我怀疑不经常这样做的原因是因为它不兼容 Haskell-98,因为它需要 EmptyDataDecls 扩展。
编辑:你不能在 Void 上进行模式匹配,但seq
会毁了你的一天。感谢@sacundim 指出这一点。
好吧,底部类型的字面意思是无限的计算,而单元类型就是它的本来面目——一种包含单个值的类型。显然,monadic 计算通常意味着要完成,所以让它们 return 根本没有意义undefined
。而且,当然,这只是一种安全措施——就像约翰 L 说的,如果有人模式匹配一元结果怎么办?所以一元计算返回“最低”可能的(在 Haskell 98 中)类型 - 单位。
所以,也许我们可以有以下签名:
mapM_ :: (Monad m) => (a -> m b) -> [a] -> m z
foldM_ :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m z
writeFile :: FilePath -> String -> IO z
我们将重新实现有问题的函数,以便任何尝试绑定z
inm z
或IO z
将变量绑定到undefined
或任何其他底部。
我们有什么收获?现在人们可以编写程序来强制执行undefined
这些计算的结果。这怎么是好事?这意味着人们现在可以编写无缘无故无法终止的程序,而这在以前是不可能编写的。
I would like to point out one severe downside to writing one particular form of returning ⊥: if you write types like this, you get bad programs:
mapM_ :: (Monad m) => (a -> m b) -> [a] -> m z
This is way too polymorphic. As an example, consider forever :: Monad m => m a -> m b
. I encountered this gotcha a long time ago and I'm still bitter:
main :: IO ()
main = forever putStrLn "This is going to get printed a lot!"
The error is obvious and simple: missing parentheses.
- It typechecks. This is exactly the sort of error that the type system is supposed to catch easily.
- It silently infinite loops at runtime (without printing anything). It is a pain to debug.
Why? Well, because r ->
is a monad. So m b
matches virtually anything. For example:
forever :: m a -> m b
forever putStrLn :: String -> b
forever putStrLn "hello!" :: b -- eep!
forever putStrLn "hello" readFile id flip (Nothing,[17,0]) :: t -- no type error.
This sort of thing inclines me to the view that forever
should be typed m a -> m Void
.