Scalaz(3)- 基础篇:函数概括化-Generalizing Functions

  Scalaz是个通用的函数式编程组件库。它提供的类型、函数组件都必须具有高度的概括性才能同时支持不同数据类型的操作。可以说,scalaz提供了一整套所有编程人员都需要的具有高度概括性的通用函数,它是通过随意多态(ad-hoc polymorphism)来帮助用户使用这些函数的。随意多态就是trait+implicit parameters+implicit conversions。简单的说就是scalaz提供一个概括化的函数,用户可以在各种类型上施用这个同一函数。概括化(generalizing)函数最基本的技巧应该是类型参数变量(parametric type variable)的使用了。如下:

1 def head[T](xs: List[T]): T = xs(0)               //> head: [T](xs: List[T])T
2 head(List(1,2,3,4))                               //> res0: Int = 1
3 head(List("a","b","c"))                           //> res1: String = a
4 case class Car(manu: String)
5 head(Car("Honda") :: Car("Toyota") :: Nil)        //> res2: scalaz.learn.ex2.Car = Car(Honda)

无论T是任何类型,Int, String, Car,都可以使用这个head函数。

但作为一个标准库的开发者,除了使用类型变量去概括函数外还必须考虑函数的使用方式以及组件库的组织结构。这篇讨论里我们将从一个组件库开发者的角度来思考、体验如何设计概括化的通用函数。

我们试着从概括化一个函数sum的过程中了解scalaz的设计思路:

针对List,Int的函数:sum(xs: List[Int]): Int

概括化后变成:sum[M[_],A](xs: M[A]): A

针对Int的sum如下:

def sum(xs: List[Int]): Int = xs.foldLeft(0){_ + _}
                                                  //> sum: (xs: List[Int])Int
sum(List(1,2,3))                                  //> res0: Int = 6
//你不能这样:sum(List(1.0,2.0,3.0)

我们看到:sum只能接受一个List[Int],其它类型过不了编译。

我们先看看这个foldLeft: 它需要一个起始值(在这里是Int 0)和一个两个值的操作(在这里是两个Int的加法)。我们可以先把这部分抽象一下:

1 object intMonoid {
2     def mappend(i1: Int, i2: Int): Int = i1 + i2
3     def mzero = 0
4 }
5 def sum(xs: List[Int]): Int = xs.foldLeft(intMonoid.mzero)(intMonoid.mappend)
6                                                   //> sum: (xs: List[Int])Int
7 sum(List(1,2,3))                                  //> res0: Int = 6

我们把这个intMonoid抽了出来。那么现在的sum已经具有了一些概括性了,因为foldLeft的具体操作依赖于我们如何定义intMonoid。

如果我们对String进行sum操作的话我可以这样:

1 object stringMonoid {
2     def mappend(s1: String, s2: String): String = s1 + s2
3     def mzero = ""
4 }
5 def sum(xs: List[String]): String = xs.foldLeft(stringMonoid.mzero)(stringMonoid.mappend)
6                                                   //> sum: (xs: List[String])String
7 sum(List("Hello,"," how are you"))                //> res0: String = Hello, how are you

按这样推敲,我们可以对任何类型A进行sum操作,只要用一个类型参数的trait就行了:

1 trait Monoid[A] {
2     def mappend(a1: A, a2: A): A
3     def mzero: A
4 }

注意具体的操作mappend和起始值都没有定义,这个会留待trait Monoid各种类型的实例里:

 1 trait Monoid[A] {
 2     def mappend(a1: A, a2: A): A
 3     def mzero: A
 4 }
 5 object intMonoid extends Monoid[Int]{
 6     def mappend(i1: Int, i2: Int): Int = i1 + i2
 7     def mzero = 0
 8 }
 9 object stringMonoid extends Monoid[String]{
10     def mappend(s1: String, s2: String): String = s1 + s2
11     def mzero = ""
12 }

现在我们可以这样改变sum:

1 def sum[A](xs: List[A])(m: Monoid[A]): A = xs.foldLeft(m.mzero)(m.mappend)
2                                                   //> sum: [A](xs: List[A])(m: scalaz.learn.ex2.Monoid[A])A
3 sum(List(1,2,3))(intMonoid)                       //> res0: Int = 6
4 sum(List("Hello,"," how are you"))(stringMonoid)  //> res1: String = Hello, how are you

现在这个sum是不是概括的多了。现在我们可以利用implicit使sum的调用表达更精炼:

 1 trait Monoid[A] {
 2     def mappend(a1: A, a2: A): A
 3     def mzero: A
 4 }
 5 implicit object intMonoid extends Monoid[Int]{
 6     def mappend(i1: Int, i2: Int): Int = i1 + i2
 7     def mzero = 0
 8 }
 9 implicit object stringMonoid extends Monoid[String]{
10     def mappend(s1: String, s2: String): String = s1 + s2
11     def mzero = ""
12 }
13 def sum[A](xs: List[A])(implicit m: Monoid[A]): A = xs.foldLeft(m.mzero)(m.mappend)
14                                                   //> sum: [A](xs: List[A])(implicit m: scalaz.learn.ex2.Monoid[A])A
15 sum(List(1,2,3))                                  //> res0: Int = 6
16 sum(List("Hello,"," how are you"))                //> res1: String = Hello, how are you

现在调用sum是不是贴切多了?按照scalaz的惯例,我们把implicit放到trait的companion object里:

 1 trait Monoid[A] {
 2     def mappend(a1: A, a2: A): A
 3     def mzero: A
 4 }
 5 object Monoid {
 6  implicit object intMonoid extends Monoid[Int]{
 7      def mappend(i1: Int, i2: Int): Int = i1 + i2
 8      def mzero = 0
 9  }
10  implicit object stringMonoid extends Monoid[String]{
11      def mappend(s1: String, s2: String): String = s1 + s2
12      def mzero = ""
13  }
14 }

这样,用户可以定义自己的Monoid实例在sum中使用。

但现在这个sum还是针对List的。我们必须再进一步概括到任何M[_]。我们先把用一个针对List的foldLeft实例来实现sum:

1 object listFoldLeft {
2     def foldLeft[A,B](xs: List[A])(b: B)(f:(B,A) => B):B = xs.foldLeft(b)(f)
3 }
4 def sum[A](xs: List[A])(implicit m: Monoid[A]): A = listFoldLeft.foldLeft(xs)(m.mzero)(m.mappend)
5                                                   //> sum: [A](xs: List[A])(implicit m: scalaz.learn.ex2.Monoid[A])A

我们可以像上面对待Monoid一样用个trait来概括M[_]:

 1 trait FoldLeft[M[_]] {
 2     def foldLeft[A,B](xs: M[A])(b: B)(f: (B,A) => B): B
 3 }
 4 object FoldLeft {
 5  implicit object listFoldLeft extends FoldLeft[List] {
 6     def foldLeft[A,B](xs: List[A])(b: B)(f:(B,A) => B):B = xs.foldLeft(b)(f)
 7  }
 8 }
 9 def sum[M[_],A](xs: M[A])(implicit m: Monoid[A], fl: FoldLeft[M]): A =
10    fl.foldLeft(xs)(m.mzero)(m.mappend)            //> sum: [M[_], A, B](xs: M[A])(implicit m: scalaz.learn.ex2.Monoid[A], implicit
11                                                   //|  fl: scalaz.learn.ex2.FoldLeft[M])A
12 sum(List(1,2,3))                                  //> res0: Int = 6
13 sum(List("Hello,"," how are you"))                //> res1: String = Hello, how are you

现在这个sum[M[_],A]是个全面概括的函数了。上面的sum也可以这样表达: 

1 def sum1[A: Monoid, M[_]: FoldLeft](xs: M[A]): A = {
2     val m = implicitly[Monoid[A]]
3     val fl = implicitly[FoldLeft[M]]
4     fl.foldLeft(xs)(m.mzero)(m.mappend)
5 }                                                 //> sum1: [A, M[_]](xs: M[A])(implicit evidence$1: scalaz.learn.ex2.Monoid[A], i
6                                                   //| mplicit evidence$2: scalaz.learn.ex2.FoldLeft[M])A

这样表达清晰多了。

在scalaz里为每个类型提供了足够的操作符号。使用这些符号的方式与普通的操作符号没有两样如 a |+| b,这是infix符号表述形式。scalaz会用方法注入(method injection)方式把这些操作方法集中放在类型名称+后缀op的trait里如,MonoidOp。我们在下面示范一下method injection。假如我设计一个使用Monoid的加法:

1 def plus[A: Monoid](a1: A, a2: A): A = implicitly[Monoid[A]].mappend(a1,a2)
2                                                   //> plus: [A](a1: A, a2: A)(implicit evidence$3: scalaz.learn.ex2.Monoid[A])A
3 plus(1,2)                                         //> res2: Int = 3
4 plus("hello ","world")                            //> res3: String = hello world

假如我想为所有类型提供一个操作符|+|,然后用 a |+| b这种方式代表plus(a,b),那么我们可以增加一个Monoid的延伸trait:MonoidOp,再把这个|+|放入:

1 trait MonoidOp[A]{
2     val M : Monoid[A]
3     val a1: A
4     def |+|(a2: A) = M.mappend(a1,a2)
5 }

现在可以用infix方式调用|+|如 a |+| b。下一步是用implicit把这个|+|方法加给任何类型A:

 1 trait MonoidOp[A]{
 2     val M : Monoid[A]
 3     val a1: A
 4     def |+|(a2: A) = M.mappend(a1,a2)
 5 }
 6 implicit def toMonoidOp[A: Monoid](a: A) = new MonoidOp[A] {
 7     val M = implicitly[Monoid[A]]
 8     val a1 = a
 9 }                                                 //> toMonoidOp: [A](a: A)(implicit evidence$3: scalaz.learn.ex2.Monoid[A])scala
10                                                   //| z.learn.ex2.MonoidOp[A]
11 1 |+| 2                                           //> res2: Int = 3
12 "hello " |+| "world"                              //> res3: String = hello world

以上所见,implicit toMonoidOp的意思是对于任何类型A,如果我们能找到A类型的Monoid实例,那么我们就可以把类型A转变成MonoidOp类型,然后类型A就可以使用操作符号|+|了。现在任何类型具备Monoid实例的类型都可以使用|+|符号了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏阿凯的Excel

Python读书笔记(数字型数据)

Python与其它编程语言一样,常见的数字型无法整型(int)和浮点型两种(Float)两种。 整型就是整数,浮点型就是小数。 如果在Python中输入一个数字...

3475
来自专栏小鹏的专栏

tf API 研读4:Inputs and Readers

tensorflow中数据的读入相关类或函数: 占位符(Placeholders) tf提供一种占位符操作,在执行时需要为其提供数据data。 操作 描...

34910
来自专栏贾老师の博客

字符编码简述

1147
来自专栏Stone的专栏

一篇文章精通 VLOOKUP 函数

相信不少人看到标题,立即嗤之以鼻,VLOOKUP 谁不会?是的,大家都会,但用的好的人不多。相信我,这篇文章一定可以算得上通俗易懂,又有深度的一篇文章,熟练掌握...

1000
来自专栏deepcc

Javascript 浮点计算问题分析与解决

863
来自专栏数据结构与算法

P2264 情书

题目背景 一封好的情书需要撰写人全身心的投入。lin_toto同学看上了可爱的卡速米想对她表白,但却不知道自己写的情书是否能感动她,现在他带着情书请你来帮助他。...

3179
来自专栏python3

Python变量类型

有符号整数,就是C语言中所指的整型,也就是数学中的整数,它的大小与安装的解释器的位数有关

722
来自专栏专知

Numpy教程第2部分 - 数据分析的重要功能

【导读】Numpy是python数据分析和科学计算的核心软件包。 上次介绍了numpy的一些基础操作。例如如何创建一个array,如何提取array元素,重塑(...

3619
来自专栏机器学习从入门到成神

数据库闭包和候选码求解方法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_35512245/articl...

761
来自专栏Petrichor的专栏

python: int函数

893

扫码关注云+社区