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

  1. import scalaz._
  2. import Scalaz._
  3. object decompose {
  4. //两个测试函数
  5. val f = (_: Int) + //> f : Int => Int = <function1>
  6. val g = (_: Int) * //> g : Int => Int = <function1>
  7. //functor
  8. val h = f map g // f andThen g //> h : Int => Int = <function1>
  9. val h1 = g map f // f compose g //> h1 : Int => Int = <function1>
  10. h() //g(f(2)) //> res0: Int = 25
  11. h1() //f(g(2)) //> res1: Int = 13
  12. //applicative
  13. val k = (f |@| g){_ + _} //> k : Int => Int = <function1>
  14. k() // f(10)+g(10) //> res2: Int = 63
  15. //monad
  16. val m = g.flatMap{a => f.map(b => a+b)} //> m : Int => Int = <function1>
  17. val n = for {
  18. a <- f
  19. b <- g
  20. } yield a + b //> n : Int => Int = <function1>
  21. m() //> res3: Int = 63
  22. n() //> res4: Int = 63
  23. }

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

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

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

  1. /** alias for `andThen` */
  2. def >=>[C](k: Kleisli[M, B, C])(implicit b: Bind[M]): Kleisli[M, A, C] = kleisli((a: A) => b.bind(this(a))(k.run))
  3.  
  4. def andThen[C](k: Kleisli[M, B, C])(implicit b: Bind[M]): Kleisli[M, A, C] = this >=> k
  5.  
  6. def >==>[C](k: B => M[C])(implicit b: Bind[M]): Kleisli[M, A, C] = this >=> kleisli(k)
  7.  
  8. def andThenK[C](k: B => M[C])(implicit b: Bind[M]): Kleisli[M, A, C] = this >==> k
  9.  
  10. /** alias for `compose` */
  11. def <=<[C](k: Kleisli[M, C, A])(implicit b: Bind[M]): Kleisli[M, C, B] = k >=> this
  12.  
  13. def compose[C](k: Kleisli[M, C, A])(implicit b: Bind[M]): Kleisli[M, C, B] = k >=> this
  14.  
  15. def <==<[C](k: C => M[A])(implicit b: Bind[M]): Kleisli[M, C, B] = kleisli(k) >=> this
  16.  
  17. 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

  1. type ReaderT[F[_], E, A] = Kleisli[F, E, A]
  2. val ReaderT = Kleisli
  3. type =?>[E, A] = Kleisli[Option, E, A]
  4. type Reader[E, A] = ReaderT[Id, E, A]
  5.  
  6. type Writer[W, A] = WriterT[Id, W, A]
  7. type Unwriter[W, A] = UnwriterT[Id, W, A]
  8.  
  9. object Reader {
  10. def apply[E, A](f: E => A): Reader[E, A] = Kleisli[Id, E, A](f)
  11. }
  12.  
  13. object Writer {
  14. def apply[W, A](w: W, a: A): WriterT[Id, W, A] = WriterT[Id, W, A]((w, a))
  15. }
  16.  
  17. object Unwriter {
  18. def apply[U, A](u: U, a: A): UnwriterT[Id, U, A] = UnwriterT[Id, U, A]((u, a))
  19. }

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

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

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

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

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

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

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

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

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

下面是一组模拟数据:

  1. val data: List[Continent] = List(
  2. Continent("Europe"),
  3. Continent("America",
  4. List(
  5. Country("USA",
  6. List(
  7. City("Washington"), City("New York"))))),
  8. Continent("Asia",
  9. List(
  10. Country("India",
  11. List(City("New Dehli"), City("Calcutta"))))))

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

  1. def continents(name: String): List[Continent] =
  2. data.filter(k => k.name.contains(name)) //> continents: (name: String)List[Exercises.kli.Continent]
  3. //查找名字包含A的continent
  4. continents("A") //> res7: List[Exercises.kli.Continent] = List(Continent(America,List(Country(U
  5. //| SA,List(City(Washington,false,20), City(New York,false,20))))), Continent(A
  6. //| sia,List(Country(India,List(City(New Dehli,false,20), City(Calcutta,false,2
  7. //| 0))))))
  8. //找到两个:List(America,Asia)
  9. def countries(continent: Continent): List[Country] = continent.countries
  10. //> countries: (continent: Exercises.kli.Continent)List[Exercises.kli.Country]
  11. //查找America下的国家
  12. val america =
  13. Continent("America",
  14. List(
  15. Country("USA",
  16. List(
  17. City("Washington"), City("New York")))))
  18. //> america : Exercises.kli.Continent = Continent(America,List(Country(USA,Lis
  19. //| t(City(Washington,false,20), City(New York,false,20)))))
  20. countries(america) //> res8: List[Exercises.kli.Country] = List(Country(USA,List(City(Washington,f
  21. //| alse,20), City(New York,false,20))))
  22. def cities(country: Country): List[City] = country.cities
  23. //> cities: (country: Exercises.kli.Country)List[Exercises.kli.City]
  24. val usa = Country("USA",
  25. List(
  26. City("Washington"), City("New York")))
  27. //> usa : Exercises.kli.Country = Country(USA,List(City(Washington,false,20),
  28. //| City(New York,false,20)))
  29. cities(usa) //> res9: List[Exercises.kli.City] = List(City(Washington,false,20), City(New Y
  30. //| ork,false,20))

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

String => List[Continent]

Continent => List[Country]

Country => List[City]

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

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

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

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

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

  1. allCity =<< List("Amer","Asia") //> res12: List[Exercises.kli.City] = List(City(Washington,false,20), City(New
  2. //| 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]]

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

  1. //查找Continent List[String] => Option[List[Continent]]
  2. def maybeContinents(names: List[String]): Option[List[Continent]] =
  3. names.flatMap(name => data.filter(k => k.name.contains(name))) match {
  4. case h :: t => (h :: t).some
  5. case _ => none
  6. } //> maybeContinents: (names: List[String])Option[List[Exercises.kli.Continent]]
  7. //|
  8. //测试运行
  9. maybeContinents(List("Amer","Asia")) //> res13: Option[List[Exercises.kli.Continent]] = Some(List(Continent(America,
  10. //| List(Country(USA,List(City(Washington,false,20), City(New York,false,20))))
  11. //| ), Continent(Asia,List(Country(India,List(City(New Dehli,false,20), City(Ca
  12. //| lcutta,false,20)))))))
  13. //查找Country List[Continent] => Option[List[Country]]
  14. def maybeCountries(continents: List[Continent]): Option[List[Country]] =
  15. continents.flatMap(continent => continent.countries.map(c => c)) match {
  16. case h :: t => (h :: t).some
  17. case _ => none
  18. } //> maybeCountries: (continents: List[Exercises.kli.Continent])Option[List[Exer
  19. //| cises.kli.Country]]
  20. //查找City List[Country] => Option[List[Country]]
  21. def maybeCities(countries: List[Country]): Option[List[City]] =
  22. countries.flatMap(country => country.cities.map(c => c)) match {
  23. case h :: t => (h :: t).some
  24. case _ => none
  25. } //> maybeCities: (countries: List[Exercises.kli.Country])Option[List[Exercises.
  26. //| kli.City]]
  27.  
  28. val maybeAllCities = kleisli(maybeContinents) >==> maybeCountries >==> maybeCities
  29. //> maybeAllCities : scalaz.Kleisli[Option,List[String],List[Exercises.kli.Cit
  30. //| y]] = Kleisli(<function1>)
  31. maybeAllCities(List("Amer","Asia")) //> res14: Option[List[Exercises.kli.City]] = Some(List(City(Washington,false,2
  32. //| 0), City(New York,false,20), City(New Dehli,false,20), City(Calcutta,false,
  33. //| 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 软件项目非法模块与功能的管理与  监狱管理的对比 原理与概论attilax总结

    Atitit 软件项目非法模块与功能的管理与  监狱管理的对比 原理与概论attilax总结 软件项目中的非法模块非法功能非法分子与人类中的非法分子很是相似,必须要建议不同的的约束管理标准化... 软 ...

  2. iOS---类方法(静态方法)和实例方法

    类方法   实例方法是以+开头的方法, 实例方法是用实例对象访问:   类方法的对象是类而不是实例,通常用来创建对象或者工具类.     在实例方法里,根据继承原理发送消息给self和super其实都 ...

  3. spring容器对bean生命周期的管理三中方式

    spring容器对bean的生命周期管理主要在两个时间点:bean的初始化完成(包括属性值被完全注入),bean的销毁(程序结束,或者引用结束)方式一:使用springXML配置中的init-meth ...

  4. 每天一个linux命令(20):find命令之exec

    find是我们很常用的一个Linux命令,但是我们一般查找出来的并不仅仅是看看而已,还会有进一步的操作,这个时候exec的作用就显现出来了. exec解释: -exec  参数后面跟的是command ...

  5. Android 在线SDK更新 和谐被墙解决

    Failed to fetch URL https://dl-ssl.google.com/android/repository/addons_list-2.xml, reason: Connect ...

  6. 玩转Django的POST请求 CSRF

    玩转Django的POST请求 CSRF 不少麻油们玩django都会碰到这个问题,POST请求莫名其妙的返回 403 foribidden,希望这篇博文能解答所有问题 三种方法 To enable ...

  7. IE浏览器下常见的CSS兼容问题

    目录 [1]宽高bug [2]边框bug [3]盒模型bug[4]列表项bug [5]浮动bug [6]定位bug [7]表单bug 宽高bug [1]IE6-浏览器下子元素能撑开父级设置好的宽高 & ...

  8. 将数据转化成字符串时:用字符串的链接 还是 StringBuilder

    /* 目的:将数据转化成字符串时:用字符串的链接 还是 StringBuilder呢? */ public class Test{ public static void main(String[] a ...

  9. 面试准备 - 最大堆的Csharp实现

    面试中最常见的问题之一...在N个数中间寻找前K大个元素 最常见的解法就是最大堆 时间复杂度O(N*log(K)) 空间复杂度O(k) 实现了一个最简单的最大堆,每次有元素进来都和堆顶元素比较一下,如 ...

  10. MySQL数据库工具类之——DataTable批量加入MySQL数据库(Net版)

    MySQL数据库工具类之——DataTable批量加入数据库(Net版),MySqlDbHelper通用类希望能对大家有用,代码如下: using MySql.Data.MySqlClient; us ...