[haskell] monods
最近在看 “all about monods”, 其中第一章举了一个例子,用以说明monod的引入是非常合乎常理的,一点都不让人惊讶,它的例子是这样的:
假设有一个data type名叫Sheep,现在有一个需求是求Sheep的父亲,我们很快就把函数写出来了:
father :: Sheep –> Sheep father sheep = sheep的老爸
函数写完了,很高兴,可以拿给QA测试了。第二天QA就怒气冲冲的跑过来说:过不了单元测试!father 多利羊 = 多利羊的老爸,你丫到底上过高中没啊,多利羊是克隆的!你看,纯函数式语言在real world里总是这么尴尬,例外情况太多,一个输入对应一个确定的输出实在很难做到。抱怨是没有用的,老老实实改代码吧。这回我们把father函数的返回值改成Maybe Sheep:
father:: Sheep –> Maybe Sheep father cloneSheep = Nothing father other = Just other的老爸
得,函数终于工作了。但是好事多磨,新的需求又来了,他们想要一个求爷爷的函数!马上写出来
grandFather :: Sheep –> Maybe Sheep grandFather sheep = case (father sheep) of Nothing -> Nothing Just papa -> case papa of Nothing -> Nothing Just sheep -> father sheep
已经有些不安了对吧,这才到爷爷呢,就两层缩进了,可以预料到的曾祖父需求将会有三层缩进,高祖父需求将有四层缩进,倘若哪天你想知道这只羊在唐朝时的祖先是谁,写个函数?好吧,祝你好运。
我们都知道代码不是这么写的,无论如何你得把某些东西抽象出来。上面的father函数和grandFather函数的问题在于,它们没把Nothing和Just sheep这个细节包圆了,信息一旦泄露出来,想控制它就难了,所以我们得改代码。
先观察上面的grandFather函数,倘若father函数可以接收sheep,也可以接收Nothing,那么在获得Just papa后要处理的那两个case分支就可以合并成一个了,比如我们这样修改:
father :: Maybe Sheep -> Maybe Sheep father Nothing -> Nothing father Just clone -> Nothing father Just other -> other的老爸
我们的爷爷函数就可以改成:
grandFather :: Maybe Sheep -> Maybe Sheep grandFather m = father $ father m
然后事情就好办多了,曾祖父就是father $ father $ father m, 高祖父就是father $ father $ father $ father m,你就可劲加$吧,总能追溯到唐朝祖先的。然而如前面所讲,real world是如此的恶心,以至于总能在你的代码找到瑕疵,比如我们把父系的问题解决了,那母系呢?你想找羊的舅舅呢?羊的表弟呢?你得写mother函数,brother函数,sister函数,等等一系列,每一个都得跟father函数一样,老老实实区分 Nothing, Just clone 和 Just other。你知道。。。我们不能这么写代码。这里的问题是,我们虽然把区分Nothing和Just m的工作被隐藏在father函数里面了,但这个隐藏的逻辑不能被复用,我们应该把这个区分的逻辑给提出来,好让大家都来用。
我们应该把father函数拆成两个,一个是单纯的求关系的函数,一个是过滤函数,求关系的函数不需要区分输入参数是什么,它就认定输入的不是Nothing,也不是多利羊,它肯定是普通的羊,比喜羊羊还普通。当然,它的输出值还是不确定的,这没办法。
father :: Sheep -> Maybe Sheep father m = m的父亲
然后再造一个comb函数,用以过滤结果,这个函数需要考虑各种情况,所以它的输入值和输出值都是Maybe Sheep。它还要负责保护father,sister等函数不要让它被例外情况crash掉,所以求关系的函数也要当做参数传入。
comb :: Maybe a -> ( a-> Maybe b) -> Maybe b comb Nothing _ -> Nothing comb (Just clone) _ -> Nothing comb (Just other) f -> f other
第一个参数是羊,第二个参数是求关系的函数,比如father,sister等。改完后求祖父的函数就变成
grandFather :: Sheep –> Maybe Sheep grandFather m = ( Just m ) ` comb` father `comb` father
如果要求外公,那么加一个类似father的mother函数,如法炮制即可,所以的例外情况都可以不考虑,因为comb函数已经帮我们考虑了。
写到这一步,我们已经不知不觉把monad引进来了,comb函数其实就是 >>= 函数,再加上return函数和 >>函数,它就正式晋升为monad了。所谓的monad,其实跟设计模式有异曲同工之妙,它规定了一些范式,你要是照着这范式写代码,将给你自己节省很多时间。跟设计模式一样,monad也不是强制性的,它只是一个建议。不过如果你写的是IO代码,不遵循monad只会是自找麻烦,那么多的例外情况,每个函数写一遍?拜托~~~
----------
以上就是我对monad的理解,"All about Monads" 里comb函数的引入太唐突了,让我困惑了一阵子,在询问haskell-cafe里的热心人之后总算醒悟,社区果然是个好东西