[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里的热心人之后总算醒悟,社区果然是个好东西

转载于:https://www.cnblogs.com/gussing/archive/2010/09/19/1831226.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值