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

 import scalaz._
import Scalaz._
object decompose {
//两个测试函数
val f = (_: Int) + //> f : Int => Int = <function1>
val g = (_: Int) * //> g : Int => Int = <function1>
//functor
val h = f map g // f andThen g //> h : Int => Int = <function1>
val h1 = g map f // f compose g //> h1 : Int => Int = <function1>
h() //g(f(2)) //> res0: Int = 25
h1() //f(g(2)) //> res1: Int = 13
//applicative
val k = (f |@| g){_ + _} //> k : Int => Int = <function1>
k() // f(10)+g(10) //> res2: Int = 63
//monad
val m = g.flatMap{a => f.map(b => a+b)} //> m : Int => Int = <function1>
val n = for {
a <- f
b <- g
} yield a + b //> n : Int => Int = <function1>
m() //> res3: Int = 63
n() //> res4: Int = 63
}

以上的函数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

 final case class Kleisli[M[_], A, B](run: A => M[B]) { self =>
...
trait KleisliFunctions {
/**Construct a Kleisli from a Function1 */
def kleisli[M[_], A, B](f: A => M[B]): Kleisli[M, A, B] = Kleisli(f)
...

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

   /** alias for `andThen` */
def >=>[C](k: Kleisli[M, B, C])(implicit b: Bind[M]): Kleisli[M, A, C] = kleisli((a: A) => b.bind(this(a))(k.run)) def andThen[C](k: Kleisli[M, B, C])(implicit b: Bind[M]): Kleisli[M, A, C] = this >=> k def >==>[C](k: B => M[C])(implicit b: Bind[M]): Kleisli[M, A, C] = this >=> kleisli(k) def andThenK[C](k: B => M[C])(implicit b: Bind[M]): Kleisli[M, A, C] = this >==> k /** alias for `compose` */
def <=<[C](k: Kleisli[M, C, A])(implicit b: Bind[M]): Kleisli[M, C, B] = k >=> this def compose[C](k: Kleisli[M, C, A])(implicit b: Bind[M]): Kleisli[M, C, B] = k >=> this def <==<[C](k: C => M[A])(implicit b: Bind[M]): Kleisli[M, C, B] = kleisli(k) >=> this def composeK[C](k: C => M[A])(implicit b: Bind[M]): Kleisli[M, C, B] = this <==< k

拿操作函数>=>(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]
val ReaderT = Kleisli
type =?>[E, A] = Kleisli[Option, E, A]
type Reader[E, A] = ReaderT[Id, E, A] type Writer[W, A] = WriterT[Id, W, A]
type Unwriter[W, A] = UnwriterT[Id, W, A] object Reader {
def apply[E, A](f: E => A): Reader[E, A] = Kleisli[Id, E, A](f)
} object Writer {
def apply[W, A](w: W, a: A): WriterT[Id, W, A] = WriterT[Id, W, A]((w, a))
} object Unwriter {
def apply[U, A](u: U, a: A): UnwriterT[Id, U, A] = UnwriterT[Id, U, A]((u, a))
}

type ReaderT[F[_], E, A] = Kleisli[F, E, A] >>> type Reader[E,A] = ReaderT[Id,E,A]

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

 //Kleisli款式函数kf,kg
val kf: Int => Option[String] = (i: Int) => Some((i + ).shows)
//> kf : Int => Option[String] = <function1>
val kg: String => Option[Boolean] = { case "" => true.some; case _ => false.some }
//> kg : String => Option[Boolean] = <function1>
//Kleisli函数组合操作
import Kleisli._
val kfg = kleisli(kf) >=> kleisli(kg) //> kfg : scalaz.Kleisli[Option,Int,Boolean] = Kleisli(<function1>)
kfg() //> res5: Option[Boolean] = Some(false)
kfg() //> res6: Option[Boolean] = Some(true)

例子虽然很简单,但它说明了很多重点:上一个函数输入的运算值是下一个函数的输入值 Int=>String=>Boolean。输出Monad一致统一,都是Option。

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

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

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

   // just some trivial data structure ,
// Continents contain countries. Countries contain cities.
case class Continent(name: String, countries: List[Country] = List.empty)
case class Country(name: String, cities: List[City] = List.empty)
case class City(name: String, isCapital: Boolean = false, inhabitants: Int = )

分别是:洲(Continent)、国家(Country)、城市(City)。它们之间的关系是层级的:Continent(Country(City))

下面是一组模拟数据:

  val data: List[Continent] = List(
Continent("Europe"),
Continent("America",
List(
Country("USA",
List(
City("Washington"), City("New York"))))),
Continent("Asia",
List(
Country("India",
List(City("New Dehli"), City("Calcutta"))))))

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

   def continents(name: String): List[Continent] =
data.filter(k => k.name.contains(name)) //> continents: (name: String)List[Exercises.kli.Continent]
//查找名字包含A的continent
continents("A") //> res7: List[Exercises.kli.Continent] = List(Continent(America,List(Country(U
//| SA,List(City(Washington,false,20), City(New York,false,20))))), Continent(A
//| sia,List(Country(India,List(City(New Dehli,false,20), City(Calcutta,false,2
//| 0))))))
//找到两个:List(America,Asia)
def countries(continent: Continent): List[Country] = continent.countries
//> countries: (continent: Exercises.kli.Continent)List[Exercises.kli.Country]
//查找America下的国家
val america =
Continent("America",
List(
Country("USA",
List(
City("Washington"), City("New York")))))
//> america : Exercises.kli.Continent = Continent(America,List(Country(USA,Lis
//| t(City(Washington,false,20), City(New York,false,20)))))
countries(america) //> res8: List[Exercises.kli.Country] = List(Country(USA,List(City(Washington,f
//| alse,20), City(New York,false,20))))
def cities(country: Country): List[City] = country.cities
//> cities: (country: Exercises.kli.Country)List[Exercises.kli.City]
val usa = Country("USA",
List(
City("Washington"), City("New York")))
//> usa : Exercises.kli.Country = Country(USA,List(City(Washington,false,20),
//| City(New York,false,20)))
cities(usa) //> res9: List[Exercises.kli.City] = List(City(Washington,false,20), City(New Y
//| ork,false,20))

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

String => List[Continent]

Continent => List[Country]

Country => List[City]

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

   val allCountry = kleisli(continents) >==> countries
//> allCountry : scalaz.Kleisli[List,String,Exercises.kli.Country] = Kleisli(<
//| function1>)
val allCity = kleisli(continents) >==> countries >==> cities
//> allCity : scalaz.Kleisli[List,String,Exercises.kli.City] = Kleisli(<functi
//| on1>)
allCountry("Amer") //> res10: List[Exercises.kli.Country] = List(Country(USA,List(City(Washington,
//| false,20), City(New York,false,20))))
allCity("Amer") //> res11: List[Exercises.kli.City] = List(City(Washington,false,20), City(New
//| York,false,20))

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

   def =<<(a: M[A])(implicit m: Bind[M]): M[B] = m.bind(a)(run)

意思是用包嵌的函数flatMap一下输入参数M[A]:

   allCity =<< List("Amer","Asia")                 //> res12: List[Exercises.kli.City] = List(City(Washington,false,20), City(New
//| York,false,20), City(New Dehli,false,20), City(Calcutta,false,20))

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

List[String] => Option[List[Continent]]

List[Continent] => Option[List[Country]]

List[Country] => Option[List[City]]

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

   //查找Continent List[String] => Option[List[Continent]]
def maybeContinents(names: List[String]): Option[List[Continent]] =
names.flatMap(name => data.filter(k => k.name.contains(name))) match {
case h :: t => (h :: t).some
case _ => none
} //> maybeContinents: (names: List[String])Option[List[Exercises.kli.Continent]]
//|
//测试运行
maybeContinents(List("Amer","Asia")) //> res13: Option[List[Exercises.kli.Continent]] = Some(List(Continent(America,
//| List(Country(USA,List(City(Washington,false,20), City(New York,false,20))))
//| ), Continent(Asia,List(Country(India,List(City(New Dehli,false,20), City(Ca
//| lcutta,false,20)))))))
//查找Country List[Continent] => Option[List[Country]]
def maybeCountries(continents: List[Continent]): Option[List[Country]] =
continents.flatMap(continent => continent.countries.map(c => c)) match {
case h :: t => (h :: t).some
case _ => none
} //> maybeCountries: (continents: List[Exercises.kli.Continent])Option[List[Exer
//| cises.kli.Country]]
//查找City List[Country] => Option[List[Country]]
def maybeCities(countries: List[Country]): Option[List[City]] =
countries.flatMap(country => country.cities.map(c => c)) match {
case h :: t => (h :: t).some
case _ => none
} //> maybeCities: (countries: List[Exercises.kli.Country])Option[List[Exercises.
//| kli.City]] val maybeAllCities = kleisli(maybeContinents) >==> maybeCountries >==> maybeCities
//> maybeAllCities : scalaz.Kleisli[Option,List[String],List[Exercises.kli.Cit
//| y]] = Kleisli(<function1>)
maybeAllCities(List("Amer","Asia")) //> res14: Option[List[Exercises.kli.City]] = Some(List(City(Washington,false,2
//| 0), City(New York,false,20), City(New Dehli,false,20), City(Calcutta,false,
//| 20)))

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

Scalaz(14)- Monad:函数组合-Kleisli to Reader的更多相关文章

  1. [Java 8] (10) 使用Lambda完成函数组合,Map-Reduce以及并行化

    好文推荐!!!!! 原文见:http://blog.csdn.net/dm_vincent/article/details/40856569 Java 8中同时存在面向对象编程(OOP)和函数式编程( ...

  2. 理解函数式编程中的函数组合--Monoids(二)

    使用函数式语言来建立领域模型--类型组合 理解函数式编程语言中的组合--前言(一) 理解函数式编程中的函数组合--Monoids(二) 继上篇文章引出<范畴论>之后,我准备通过几篇文章,来 ...

  3. 复杂的 Hash 函数组合有意义吗?

    很久以前看到一篇文章,讲某个大网站储存用户口令时,会经过十分复杂的处理.怎么个复杂记不得了,大概就是先 Hash,结果加上一些特殊字符再 Hash,结果再加上些字符.再倒序.再怎么怎么的.再 Hash ...

  4. F# 可以把几个函数组合成新函数

    C#能做的,F#基本都能做,但F#能做的,C#未必能做. F#中的函数可以把几个函数组合起来使用.下面的例子是把由 function1 和 function2 这两个函数通过运算符“>>” ...

  5. Excel:11个查询函数组合

    还不懂?上栗子~ 1.普通查找 根据表二中的姓名,查找表一对应的应发工资.最基础的VLOOKUP函数就能搞定. 2.反向查找 根据表二姓名,查找表一编号.但表一中编号列在姓名列之前,无法直接使用VLO ...

  6. Java函数接口实现函数组合及装饰器模式

    摘要: 通过求解 (sinx)^2 + (cosx)^2 = 1 的若干写法,逐步展示了如何从过程式的写法转变到函数式的写法,并说明了编写"[接受函数参数]并返回[能够接受函数参数的函数]的 ...

  7. index+small+row+if经典函数组合应用

    EXCEL中index+small+row+if 函数组合可以查出满足同一条件的所有记录,通过实例讲解: 本文为原创,转载需标明出处,谢谢! 例:查找出一年级的所有班级及人数: A B C D 1 年 ...

  8. scala进阶笔记:函数组合器(combinator)

    collection基础参见之前的博文scala快速学习(二). 本文主要是组合器(combinator),因为在实际中发现很有用.主要参考:http://www.importnew.com/3673 ...

  9. 6.5.2 C# 中的函数组合

    6.5.2 C# 中的函数组合 C# 中的函数组合是可能的.但使用非常有限,这是部分是由于在 C# 中散应用不能非常easy使用.但更重要的是,由于大多数操作是用成员来写的.而不是函数.但我们至少能够 ...

随机推荐

  1. Atitit. Api 设计 原则 ---归一化

    Atitit. Api 设计 原则 ---归一化 1.1. 叫做归一化1 1.2. 归一化的实例:一切对象都可以序列化/toString  通过接口实现1 1.3. 泛文件概念.2 1.4. 游戏行业 ...

  2. Atitit 基于dom的游戏引擎

    Atitit 基于dom的游戏引擎 1. 添加sprite控件(cocos,createjs,dom)1 1.1.1. Cocos1 1.1.2. createjs1 1.1.3. Dom模式2 1. ...

  3. Atitit j2ee5 jee5 j2ee6 j2ee7 jee6 jee7 新特性

    Atitit j2ee5 jee5 j2ee6 j2ee7 jee6 jee7 新特性 Keyword Java ee5 ,Java ee6,Java ee7  j2ee5 jee5 j2ee6 j2 ...

  4. 实用的开放源码的Excel导入导出类库 CarlosAg ExcelXmlWriter

    做企业管理软件经常会遇到要把数据导出成EXCEL格式,目前市面上有很多工具类库可以实现此功能.CarlosAg ExcelXmlWriter是其中之一,它绿色小巧,免安装,又源码开放,我在项目中一直以 ...

  5. 转载:css3 content 生成内容

    本文地址:http://www.w3cplus.com/solution/css3content/css3content.html 这篇文章挺不错的,建议看一下. content一般和:before, ...

  6. javascript中的错误处理机制

    × 目录 [1]对象 [2]类型 [3]事件[4]throw[5]try[6]常见错误 前面的话 错误处理对于web应用程序开发至关重要,不能提前预测到可能发生的错误,不能提前采取恢复策略,可能导致较 ...

  7. javascript中数组和字符串的方法比较

    × 目录 [1]可索引 [2]转换 [3]拼接[4]创建[5]位置 前面的话 字符串和数组有很多的相同之处,它们的方法众多,且相似度很高:但它们又有不同之处,字符串是不可变值,于是可以把其看作只读的数 ...

  8. javascript中关于日期和时间的基础知识

    × 目录 [1]标准时间 [2]字符串 [3]闰年[4]月日[5]星期[6]时分秒 前面的话 在介绍Date对象之前,首先要先了解关于日期和时间的一些知识.比如,闰年.UTC等等.深入了解这些,有助于 ...

  9. codeforces B. Pasha and String(贪心)

    题意:给定一个长度为len的字符序列,然后是n个整数,对于每一个整数ai, 将字符序列区间为[ai,len-ai+1]进行反转.求出经过n次反转之后的序列! /* 思路1:将区间为偶数次的直接去掉!对 ...

  10. 推荐一个算法编程学习中文社区-51NOD【算法分级,支持多语言,可在线编译】

    最近偶尔发现一个算法编程学习的论坛,刚开始有点好奇,也只是注册了一下.最近有时间好好研究了一下,的确非常赞,所以推荐给大家.功能和介绍看下面介绍吧.首页的标题很给劲,很纯粹的Coding社区....虽 ...