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

来源:互联网 时间:2015-12-05

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

 1 import scalaz._

2 import Scalaz._

3 object decompose {

4 //两个测试函数

5 val f = (_: Int) + 3 //> f : Int => Int = <function1>

6 val g = (_: Int) * 5 //> 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(2) //g(f(2)) //> res0: Int = 25

11 h1(2) //f(g(2)) //> res1: Int = 13

12 //applicative

13 val k = (f |@| g){_ + _} //> k : Int => Int = <function1>

14 k(10) // 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(10) //> res3: Int = 63

22 n(10) //> 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 + 3).shows)

3 //> kf : Int => Option[String] = <function1>

4 val kg: String => Option[Boolean] = { case "3" => 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(1) //> res5: Option[Boolean] = Some(false)

10 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))

下面是一组模拟数据:

 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来实现函数组合。

 

 

 

 

 

相关阅读:
Top