天天看点

Scalaz(14)- Monad:函数组合-Kleisli to Reader

  monad reader就是一种函数的组合。在scalaz里函数(function)本身就是monad,自然也就是functor和applicative。我们可以用monadic方法进行函数组合:

 以上的函数f,g必须满足一定的条件才能实现组合。这个从f(g(2))或g(f(2))可以看出:必需固定有一个输入参数及输入参数类型和函数结果类型必需一致,因为一个函数的输出成为另一个函数的输入。在fp里这样的函数组合就是monadic reader。 

但是fp里函数运算结果一般都是m[r]这样格式的,所以我们需要对f:a => m[b],g:b => m[c]这样的函数进行组合。这就是scalaz里的kleisli了。kleisli就是函数a=>m[b]的类封套,从kleisli的类定义可以看出:scalaz/kleisli.scala

kleisli的目的是把monadic函数组合起来或者更形象说连接起来。kleisli提供的操作方法如>=>可以这样理解:

(a=>m[b]) >=> (b=>m[c]) >=> (c=>m[d]) 最终运算结果m[d]

可以看出kleisli函数组合有着固定的模式:

1、函数必需是 a => m[b]这种模式;只有一个输入,结果是一个monad m[_]

2、上一个函数输出m[b],他的运算值b就是下一个函数的输入。这就要求下一个函数的输入参数类型必需是b

3、m必须是个monad;这个可以从kleisli的操作函数实现中看出:scalaz/kleisli.scala

拿操作函数>=>(andthen)举例:implicit b: bind[m]明确了m必须是个monad。

kleisli((a: a) => b.bind(this(a))(k.run))的意思是先运算m[a],接着再运算k,以m[a]运算结果值a作为下一个函数k.run的输入参数。整个实现过程并不复杂。

实际上reader就是kleisli的一个特殊案例:在这里kleisli的m[]变成了id[],因为id[a]=a >>> a=>id[b] = a=>b,就是我们上面提到的reader,我们看看reader在scalaz里是如何定义的:scalar/package.scala

type readert[f[_], e, a] = kleisli[f, e, a] >>> type reader[e,a] = readert[id,e,a]

好了,说了半天还是回到如何使用kleisli进行函数组合的吧:

例子虽然很简单,但它说明了很多重点:上一个函数输入的运算值是下一个函数的输入值 int=>string=>boolean。输出monad一致统一,都是option。

那么,kleisli到底用来干什么呢?它恰恰显示了fp函数组合的真正意义:把功能尽量细分化,通过各种方式的函数组合实现灵活的函数重复利用。也就是在fp领域里,我们用kleisli来组合fp函数。

下面我们就用scalaz自带的例子scalaz.example里的kleisliusage.scala来说明一下kleisli的具体使用方法吧:

下面是一组地理信息结构:

分别是:洲(continent)、国家(country)、城市(city)。它们之间的关系是层级的:continent(country(city))

下面是一组模拟数据:

从上面的模拟数据也可以看出continent,country,city之间的隶属关系。我们下面设计三个函数分别对continent,country,city进行查找:

从continents,countries,cities这三个函数运算结果可以看出它们都可以独立运算。这三个函数的款式如下:

string => list[continent]

continent => list[country]

country => list[city]

无论函数款式或者类封套(list本来就是monad)都适合kleisli。我们可以用kleisli把这三个局部函数用各种方法组合起来实现更广泛功能:

还有个=<<符号挺有意思:

意思是用包嵌的函数flatmap一下输入参数m[a]:

那么如果我想避免使用list(),用option[list]作为函数输出可以吗?option是个monad,第一步可以通过。下一步是把函数款式对齐了:

list[string] => option[list[continent]]

list[continent] => option[list[country]]

list[country] => option[list[city]]

下面是这三个函数的升级版:

我们看到,只要monad一致,函数输入输出类型匹配,就能用kleisli来实现函数组合。