Type constructors
Maybe a monad
A list is also a monad
An example
Summary
这一章将使用 Maybe类型, 继续阅读前最好先熟悉Maybe的定义与使用。
为了理解 Haskell中的 Monad,你需要对类型构建子感到适应。一个类型构建子是一个使用了多态的参数化类型定义。Haskell中, 给一个类型构建子一个具体类型后,这个构建子就可以构造出一个新的具体的类型。 在Maybe类型的定义中:
data Maybe a = Nothing | Just a
Maybe是一个类型构建子,Nothing和Just是data构建子。你可以给 Just构造子应用一个值来构造一个Maybe的值:
country = Just "China" (译注:原文就是 China,不知道作者与中国有何关联)
同样,你可以给Maybe类型应用一个类型来构建一个新的类型:
lookupAge :: DB -> String -> Maybe Int
多态类型就像一个可以装不同类型的值得容器。Maybe Int 可以想象成一个可以装 Int 值(或者 Nothing) 的Maybe容器, Maybe String 是可以装String值(或者Nothing)的Maybe容器。在Haskell中,我们也可以把容器本身也多态,所以我们可以写成 "m a" 来表示一个某种类型的容器装了某种类型的值!
(译注: Maybe a 将 Maybe 用 m 代替,就成了 m a)
我们经常使用类型构建子和类型变量来描述一个计算的抽象属性。比如,多态类型 Maybe a 是所有会返回一个值或不返回值的计算的类型。在这种情况下,我们可以抛开容器中包含的内容细节而谈论这个容器的属性。
如果使用monad的时候遇到编译器错误提示“kind errors" ,说明你在使用类型构建子的时候不正确。
Haskell中一个monad表示成类型构造子(称为m);一个函数来构造这种类型(a->m a);一个函数来把这种类型(m a)的值与一个使用a类型产生m a类型的计算来组合起来。(译注,这一点比较绕,看函数的类型定义体会: (m a -> (a -> m b) -> m b) )一般习惯上涨讨论monad的时候把类型构造子称为"m"。把值构造成一个monad类型的函数传统上称为“return”,第三个函数是“bind”不过写作">>="。函数签名如下:
-- monad m 类型
data m a = ...
-- return 创建一个monad数据的实例
return :: a -> m a
-- bind组合一个monad实例与一个产生另一个monad实例的函数
(>>=) :: m a -> (a -> m b) -> m b
(译注:这一段翻译的比较绕,实际上,我强烈推荐大家看原文,自己体会,毕竟教程开头就说了,不期望读一遍就明白,保留原文如下:)
In Haskell a monad is represented as a type constructor (call it m), a function that builds values of that type (a -> m a), and a function that combines values of that type with computations that produce values of that type to produce a new computation for values of that type (m a -> (a -> m b) -> m b). Note that the container is the same, but the type of the contents of the container can change. It is customary to call the monad type constructor "m" when discussing monads in general. The function that builds values of that type is traditionally called "return" and the third function is known as "bind" but is written ">>=". The signatures of the functions are:
粗略的说,monad类型构建子定义了一个计算的类型,return 函数创建这个计算类型的一个元素, >>= bind 把这个类型的计算组合中一起来构造这种类型的更加复杂的计算。用容器来做类比,娄星构建子m是一个容器可以装不同的值。 M a 是一个装a类型值的容器。Return 函数把一个值装进这个容器。>>= 函数把值从monad容器中取出来,传给一个函数产生一个装载新值的monad容器(值类型可能不同)。>>= 也叫做 "bind" 函数因为它把monad容器中的值绑定到一个函数的第一个参数上。给bind函数增加逻辑,一个monad可以实现特别的组合计算策略。
看来下面的例子就清楚了, 如果还是觉得特别糊涂的话,可以先看看这个物理类比:
http://www.haskell.org/all_about_monads/html/analogy.html
假设我们写个程序来对克隆羊实验进行跟踪。我们当然想知道我们的每只羊的基因历史,因此我们需要 mother, father 函数。但是,因为是克隆羊,因此他们不是总有mother和father 函数。
我们在Haskell程序中用 Maybe 类型来表示不含有mother 或 father的可能性:
(译注:我非常怀疑原作者非常想用人来做例子,那样确实自然多了)
type Sheep = ...
father :: Sheep -> Maybe Sheep
father = ...
mother :: Sheep -> Maybe Sheep
mother = ...
查找外祖父的函数有一点复杂,因为我们要处理没有母亲的可能:
maternalGrandfather :: Sheep -> Maybe Sheep
maternalGrandfather s = case (mother s) of
Nothing -> Nothing
Just m -> father m
继续其他的组合函数:我们会发现要求曾祖父更加的麻烦:
mothersPaternalGrandfather :: Sheep -> Maybe Sheep
mothersPaternalGrandfather s = case (mother s) of
Nothing -> Nothing
Just m -> case (father m) of
Nothing -> Nothing
Just gf -> father gf
(这样的代码)除了丑陋、不清楚、难以维护以外,工作量太大了。显然计算中任何点出现Nothing值,整个计算结果就是Nothing,如果把这个观念抽出来放到一个单独的地方,会比它分散在代码各处单独测试好的多。这将会使得代码更容易编写、阅读与修改。好的编程风格是我们创建combinator 来捕获我们希望的行为。(代码示例 example1.hs)
(译注: 果然,作者确实是想拿人举例子的,证据就在 example1.hs里)
-- comb is a combinator for sequencing operations that return Maybe comb :: Maybe a -> (a -> Maybe b) -> Maybe b comb Nothing _ = Nothing comb (Just x) f = f x -- now we can use `comb` to build complicated sequences mothersPaternalGrandfather :: Sheep -> Maybe Sheep mothersPaternalGrandfather s = (Just s) `comb` mother `comb` father `comb` father
Combinator非常成功!代码要干净的多,也更容易写更容易理解和修改。注意 comb 函数也是完全多态的--它并不是只特定对 Sheep的。实际上,这个 combinator 捕获了可能会返回失败的计算的一个通用的策略。这样,我们也可以用同样的combinator来应用到其他可能会返回失败的计算中,比如数据库查询或者字典查找。
一个很开心的结果一个普通的程序实践指引我们构建了一个monad,甚至我们没有特地实现它。Maybe类型构建子及Just函数(扮演return的角色)和我们的 combinator(扮演 >>=)一起组成了一个简单的monad,这个monad可以来建立可能会返回失败的计算。剩下的事情就是让这个monad可以和haskell中的monad框架和谐共处。这是下一章的主题。
你已经看到了Maybe类型构造子是用来构造可能会返回失败的计算的Monad。你可能会很惊讶另外一个很通用的Haskell类型 [] (list列表)也是一个monad。List Monad可以让我们构建一个可以返回0,1 或更多值的计算。
List 的 return函数简单的创建一个单元素的列表(return x = [x])。List的 bind操作把函数应用到列表原先的所有值上,创建一个新的列表(l >>= f = concatMap f l)。
返回列表的函数的一个应用是表示非确定计算--产生0,1或更多可能值得计算。在一个由非确定子计算构成的计算中,非确定性可以混合,或最终解出一个可能的结果或者根本没结果。在这个过程中,计算的可能状态表示为一个列表。List Monad 表达了一种多种可能的非确定计算路径同时计算的策略。
List Monad的这种应用的例子,以及相对的Maybe monad使用的例子不久将给出。不过首先让我们看一下Haskell中定义的monad多么有用。
我们看到了,一个monad是一个类型构建子,一个return函数,和一个叫bind(>>=)的组合函数。这三个元素一起工作来封装一个构造更复杂计算的的组合策略。
使用Maybe类型构建子,我们看到了好的编程实践让我们定义了一个简单的monad,可以用来构建复杂的一串可能失败的计算。结果Maybe monad封装了一个可能不返回值的计算策略。通过把这个策略编码到 monad中,我们获得了比用ad-hoc方式组合计算更具模块性和灵活性的方法。
我们也看到了另外一个通用的Haskell数据[],也是一个monad。List monad封装了可以返回0,1后者更多值得一个组合计算的策略。
Comments
Aside from being ugly,
Aside from being ugly, unclear, and difficult to maintain, this is just too much work.
要避免这样丑陋的、不清楚的、难于维护的代码,需要很多工作。
-->
(这样的代码)除了丑陋、不清楚、难以维护以外,工作量太大了。
thanks
thanks