B/c 我开始理解 Monads - 这正是他们的目的。
这个:http://homepages.inf.ed.ac.uk/wadler/topics/monads.html#monads 是 Haskell 中关于 monads 的第一篇论文:
范畴理论家在 1960 年代发明了单子来简明地表达通用代数的某些方面。
所以你马上就可以看到 monad 与“纯”/“不纯”计算无关。最常见的单子(世界上!)是Maybe:
data Maybe a
= Nothing
| Just a
instance Monad Maybe where
return = Just
Nothing >>= f = Nothing
Just x >>= f = f x
monad 是四元组(Maybe, liftM, return, join),其中:
liftM :: (a -> b) -> Maybe a -> Maybe b
liftM f mb = mb >>= return . f
join :: Maybe (Maybe a) -> Maybe a
join Nothing = Nothing
join (Just mb) = mb
注意liftM 采用非Maybe 函数(不是纯函数!)并将其应用于Maybe,而join 采用两级Maybe 和将其简化为单层(因此结果中的Just 来自于具有两层Just:
join (Just (Just x)) = Just x
而结果中的Nothing 可以来自任一层的Nothing:
join Nothing = Nothing
join (Just Nothing) = Nothing
)。我们可以将这些术语翻译如下:
-
Maybe:可能存在也可能不存在的值。
-
liftM:如果存在,则将此函数应用于值,否则什么也不做。
-
return:获取这个存在的值并将其注入到 Maybe 的额外结构中。
-
join:取一个可能存在或不存在的(可能存在或不存在的值),并消除“可能存在或可能不存在”两层之间的区别。
现在,Maybe 是一个非常合适的数据类型。在脚本语言中,它仅使用undef 或等价物来表达Nothing,并以与x 相同的方式表示Just x。在 C/C++ 中,它使用指针类型t* 表示,并允许指针为NULL。在 Scala 中,有一个明确的容器类型:http://www.scala-lang.org/api/current/index.html#scala.Option。所以你不能说“哦,那只是异常”,因为有异常的语言仍然希望能够在不抛出异常的情况下表达“这里没有值”,然后在值存在时应用函数(这就是为什么 Scala 的 Option 类型有一个foreach 方法)。但是“如果值存在则应用此函数”正是Maybe 的>>= 所做的!所以这是一个非常有用的操作。
您会注意到,C 和脚本语言通常不允许区分 Nothing 和 Just Nothing --- 值存在或不存在。在函数式语言中——比如 Haskell——允许两个版本是很有趣的,这就是为什么我们需要join 来消除这种区别,当我们完成它时。 (而且,在数学上,用liftM 和join 来定义>>= 比用其他方式更好)。
顺便说一下,为了澄清关于 Haskell 和 IO 的常见误解:GHC 的 IO 实现包装了 GHC 的 I/O 实现的副作用。即使这也是 GHC 的一个糟糕的设计决策——命令式(这与不纯的不同!)I/O 可以在系统的任何级别上无杂质地进行单子建模。您不需要副作用(在系统的任何层)来执行 I/O!