Scalaz(14)- Monad:函数组合-Kleisli to Reader
Monad Reader就是一种函数的组合。在scalaz里函数(function)本身就是Monad,自然也就是Functor和applicative。我们可以用Monadic方法进行函数组合:
import scalaz._ import Scalaz._ object decompose { //两个测试函数 val f = (_: Int) + 3 // f : Int = Int = function1 val g = (_: Int) * 5 // 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(2) //g(f(2)) // res0: Int = 25 h1(2) //f(g(2)) // res1: Int = 13 //applicative val k = (f |@| g){_ + _} // k : Int = Int = function1 k(10) // 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(10) // res3: Int = 63 n(10) // 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
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
/** 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 + 3).shows) // kf : Int = Option[String] = function1 val kg: String = Option[Boolean] = { case "3" = 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(1) // res5: Option[Boolean] = Some(false) kfg(0) // 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 = 20)
分别是:洲(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))
还有个= 符号挺有意思:
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]]
下面是这三个函数的升级版:
//查找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来实现函数组合。
【Python函数式编程】——高阶函数(Higher-order function) 函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
一篇文章掌握lambda,function下41个类 Java8 发布以来,lambda 表达式简化了代码,增强了阅读性,代码更加简洁。lambda 主要是给Java增加了函数式编程的方式。lambda表达式的实现就是functionInterface。
相关文章
- 【HDU2865】构造矩阵+Burnside定理+欧拉函数类似poj2888[通俗易懂]
- matlab怎么将散点图的散点连线_plot函数画散点图
- cstring头文件都有什么函数_cstdlib是什么头文件
- ORA-16444: ALTER SYSTEM FLUSH REDO TO STANDBY failed due to a corrupted control file or online log file. ORACLE 报错 故障修复 远程处理
- Python len()函数详解:获取字符串长度或字节数
- Oracle to_char函数的使用方法
- Oracle to_date()函数的用法介绍
- C++字符串函数详解
- 释放Oracle中VARCHAR2/CHAR字段中的数字TO_NUMBER函数(oracle转数字函数)
- 使用 Oracle 函数返回标准输出结果表(oracle函数返回表)
- Oracle数据库中的MAX函数应用,查询最大值(oracle查询最大值)
- MySQL中如何使用IF函数(mysql中if的使用)
- 利用Oracle TO函数实现数据转换(oracle to_函数)
- Oracle下医嘱执行函数
- phpin_array函数使用说明与in_array需要注意的地方说明
- c#中oracle的to_date函数使用方法