Haskell type classes
The Monad class
Example continued
Do notation
Summary
本章的讨论涉及到Haskell的类型class系统。如果你对haskll中的class 不熟悉,需要先回顾一下这部分。
在haskell中,有一个标准的Monad class,它定义了两个monad中的函数原型return , >>=。你的monad实例并不是一定要继承自Monad class,不过最好如此。Haskell语言内建了对Monad class的特殊支持,使得你的monad实例的代码更清楚更优雅。并且使用不标准的函数名时可以给用户信息(?)。它很简单易用,而且有很多好处,所以用它好了。
Haskell中的标准 Monad class 定义类似这样:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
继续上面的例子,我们来看看 Maybe 类型构建子如何通过继承 Monad class 来适应Haskell monad系统。
回想我们的Maybe monad使用 Just数据构建子来充当monad return函数的角色,并且构造了一个简单的combinator来充当 monad >>= bind函数的角色。我们可以把Maybe声明成 Monad class的实例来明确他们的角色:
instance Monad Maybe where
Nothing >>= f = Nothing
(Just x) >>= f = f x
return = Just
一旦我们把Maybe定义成Monad class的实例,我们就可以使用标准的monad操作来构造复杂的计算:
-- 我们可以使用monad风格从操作符
maternalGrandfather :: Sheep -> Maybe Sheep maternalGrandfather s = (return s) >>= mother >>= father fathersMaternalGrandmother :: Sheep -> Maybe Sheep fathersMaternalGrandmother s = (return s) >>= father >>= mother >>= mother
在haskell的标准prelude模块中,Maybe定义为Monad class的实例,因此你无需自己来做。其他我们已经见到的monad,list也是在prelude中定义为Monad class的实例。
在写使用monad的函数时,试着使用Monad class 而不是用特定的monad实。一个
doSomething :: (Monad m) => a -> m b 这样类型的函数比 doSomething :: a-> Maybe b 这样类型的函数更灵活。前面那个函数可以通过嵌入 monad中的不同策略来获得不同的行为,而后面那个被Maybe monad限制了特定的策略。
使用标准的monad函数名很好,另一个用monad class的好处是 Haskell 支持的 “do”语法形式。 “do”形式是一个构建monadic计算的简写,就像列表内涵式(list comperhension)是建立列表计算的简写一样。Haskell中Monad class的每一个实例都可以使用do程序块。
简单讲,do形式允许用一种带有命名变量的伪-命令式风格来写monadic计算。一个monadic计算的结果可以通过 <- 操作符“分配”给一个变量。然后自动bind这个变量用在后面的 monadic计算中。<- 右边的表达式的类型是一个monad类型 m a。<-左边的表达式是一个模式,来匹配monad内部的值。(x:xs)可以匹配 Maybe [1,2,3], 比如:
这里是一个Maybe monad使用do形式的例子:代码: example2.hs
-- 我们可以用do-形式来建立复杂的顺序计算
mothersPaternalGrandfather :: Sheep -> Maybe Sheep
mothersPaternalGrandfather s = do m <- mother s
gf <- father m
father gf
与不使用do形式的 fathersMaternalGrandmother 比较一下。
上面的 do 程序块用的layout布局定义的方式。Haskell也允许使用大括号和分号来定义do程序块。
mothersPaternalGrandfather s = do { m <- mother s; gf <- father m; father gf }
注意 do形式类似于命令式程序语言,在其中的计算是由一串相似的计算显式的串行的而成。从这个角度看,monad提供了一种在大地函数式程序中使用命令式程序的可能。当我们后面处理IO和副作用的时候还要展开这个主题。
Do形式是一个简单的语法糖。没有什么do可以做而只用标准monad操作符无法完成的。不过do形式更加清晰,在某些情况下也更方便,尤其是monad计算序列很长的时候。 你要理解标准的monad bind记号和do形式,并可以在合适的情况中应用每一个。
从do形式到monad操作符的实际转换,粗略的说说每一个匹配一个模式的表达式 x<- expr1 变为: expr1 >>= \x -> ,每一个没有变量赋值的表达式 expr2 变为 expr2 >>= \_ -> 。
所有的 do 块最后以一个 monad 表达式结束,可以在do块开始的时候使用 let 子句(do中的 let子句不使用“in”关键字)。上面的 mothersPaternalGrandfather 函数定义可以转换成:
mothersPaternalGrandfather s = mother s >>= \m ->
father m >>= \gf ->
father gf
(译注: \x -> 是 lambda函数的生命方法, \_ -> 则是一个忽略传入参数的lambda函数,这个函数相当于 bind 函数声明中 (m a -> m b) 这个参数)
现在清楚了为什么 bind 操作符如此命名了。它是把monad中的值绑定到随后的lambda函数表达式的参数的字面意思。
Haskell提供了内建monad的内建支持。要获得Haskell的monad支持的优势,你需要把你的monad类型声明成Monad class的实例,并提供 return 和 >>= (读为“bind”)的定义。
Monad class的实例monad可以使用do-形式,这种“语法糖”提供了简单的,命令式风格的记号来描述monad的计算。
Recent comments
2 weeks 2 days ago
2 weeks 4 days ago
6 weeks 2 days ago
7 weeks 1 day ago
7 weeks 2 days ago
8 weeks 3 days ago
8 weeks 3 days ago
8 weeks 4 days ago
10 weeks 21 hours ago
10 weeks 3 days ago