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

 def head[T](xs: List[T]): T = xs()               //> head: [T](xs: List[T])T
head(List(,,,)) //> res0: Int = 1
head(List("a","b","c")) //> res1: String = a
case class Car(manu: String)
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(){_ + _}
//> sum: (xs: List[Int])Int
sum(List(,,)) //> res0: Int = 6
//你不能这样:sum(List(1.0,2.0,3.0)

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

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

 object intMonoid {
def mappend(i1: Int, i2: Int): Int = i1 + i2
def mzero =
}
def sum(xs: List[Int]): Int = xs.foldLeft(intMonoid.mzero)(intMonoid.mappend)
//> sum: (xs: List[Int])Int
sum(List(,,)) //> res0: Int = 6

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

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

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

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

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

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

 trait Monoid[A] {
def mappend(a1: A, a2: A): A
def mzero: A
}
object intMonoid extends Monoid[Int]{
def mappend(i1: Int, i2: Int): Int = i1 + i2
def mzero =
}
object stringMonoid extends Monoid[String]{
def mappend(s1: String, s2: String): String = s1 + s2
def mzero = ""
}

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

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

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

 trait Monoid[A] {
def mappend(a1: A, a2: A): A
def mzero: A
}
implicit object intMonoid extends Monoid[Int]{
def mappend(i1: Int, i2: Int): Int = i1 + i2
def mzero =
}
implicit object stringMonoid extends Monoid[String]{
def mappend(s1: String, s2: String): String = s1 + s2
def mzero = ""
}
def sum[A](xs: List[A])(implicit m: Monoid[A]): A = xs.foldLeft(m.mzero)(m.mappend)
//> sum: [A](xs: List[A])(implicit m: scalaz.learn.ex2.Monoid[A])A
sum(List(,,)) //> res0: Int = 6
sum(List("Hello,"," how are you")) //> res1: String = Hello, how are you

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

 trait Monoid[A] {
def mappend(a1: A, a2: A): A
def mzero: A
}
object Monoid {
implicit object intMonoid extends Monoid[Int]{
def mappend(i1: Int, i2: Int): Int = i1 + i2
def mzero =
}
implicit object stringMonoid extends Monoid[String]{
def mappend(s1: String, s2: String): String = s1 + s2
def mzero = ""
}
}

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

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

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

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

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

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

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

这样表达清晰多了。

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

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

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

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

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

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

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

Scalaz(3)- 基础篇:函数概括化-Generalizing Functions的更多相关文章

  1. Template 基础篇-函数模板(待看

    Template 基础篇-函数模板 Template所代表的泛型编程是C++语言中的重要的组成部分,我将通过几篇blog对这半年以来的学习做一个系统的总结,本文是基础篇的第一部分. Template ...

  2. SQL基础篇---函数及其函数配套使用的关键字

    一.数值函数 知识点1 SUM 求总和 SELECT breakfast,sum(price) FROM my_foods GROUP BY breakfast ORDER BY SUM(price) ...

  3. 小猪猪C++笔记基础篇(六)参数传递、函数重载、函数指针、调试帮助

    小猪猪C++笔记基础篇(六) ————参数传递.函数重载.函数指针.调试帮助 关键词:参数传递.函数重载.函数指针.调试帮助 因为一些事情以及自己的懒惰,大概有一个星期没有继续读书了,已经不行了,赶紧 ...

  4. 前端总结·基础篇·JS(三)arguments、callee、call、apply、bind及函数封装和构造函数

    前端总结系列 前端总结·基础篇·CSS(一)布局 前端总结·基础篇·CSS(二)视觉 前端总结·基础篇·CSS(三)补充 前端总结·基础篇·JS(一)原型.原型链.构造函数和字符串(String) 前 ...

  5. HTML5和CSS3扁平化风格博客(基础篇)

    多学一点总是好的~ 自始至终都觉得的css和html效果比较美观,于是在看慕课网教程时,自己也跟着敲了深爱着的前端代码 这部分分为两部分:①基础篇:http://www.imooc.com/learn ...

  6. Python基础篇(三)_函数及代码复用

    Python基础篇_函数及代码复用 函数的定义.使用: 函数的定义:通过保留字def实现. 定义形式:def <函数名>(<参数列表>): <函数体> return ...

  7. PHP丨PHP基础知识之PHP基础入门——函数「理论篇」

    前两天讲过PHP基础知识的判断条件和流程控制,今天来讲讲PHP基础知识之PHP基础入门--函数! 一.函数的声明与使用 1.函数名是标识符之一,只能有数字字母下划线,开头不能是数字. 函数名的命名,须 ...

  8. 夯实Java基础系列1:Java面向对象三大特性(基础篇)

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 [https://github.com/h2pl/Java-Tutorial](https: ...

  9. 《量化投资:以MATLAB为工具》连载(2)基础篇-N分钟学会MATLAB(中)

    http://www.matlabsky.com/thread-43937-1-1.html   <量化投资:以MATLAB为工具>连载(3)基础篇-N分钟学会MATLAB(下)     ...

随机推荐

  1. Atitit 图像处理之仿油画效果 Oilpaint油画滤镜 水彩画 漫画滤镜 v2

    Atitit 图像处理之仿油画效果 Oilpaint油画滤镜 水彩画 漫画滤镜 v2 1.1. 具体源码参考1 2. ,油画 水彩画具有几个比较明显的特点如下:1 2.1. 明暗层次(灰度)较少  也 ...

  2. meta标签的小拓展

    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, ...

  3. gridview里日期显示格式

    Text='<%#Bind("EndDate","{0:yyyy-MM-dd}") %>'

  4. javascript类型系统——Math对象

    × 目录 [1]常量 [2]函数 前面的话 javascript使用算术运算符实现基本的算术运算,如果要实现更加复杂的算术运算,需要通过Math对象定义的常量和函数来实现.和其他对象不同,Math只是 ...

  5. Java多线程系列--“基础篇”09之 interrupt()和线程终止方式

    概要 本章,会对线程的interrupt()中断和终止方式进行介绍.涉及到的内容包括:1. interrupt()说明2. 终止线程的方式2.1 终止处于“阻塞状态”的线程2.2 终止处于“运行状态” ...

  6. Yii的学习(1)--安装配置

    之前在sina博客写过Yii的文章,来到博客园之后,没再写过关于Yii的文章,正好端午假期没啥事,就结合以前的博客,Yii的官方文档,再加上最近的关于Yii的收获总结一下,写个系列~~ Yii是一个基 ...

  7. 单机redis 主从实例

    环境windows xp sp3 1.redis 安装 redis windows安装文件下载地址:http://code.google.com/p/servicestack/wiki/RedisWi ...

  8. jQuery判断当前元素显示状态并控制元素的显示与隐藏

    1.jQuery判断一个元素当前状态是显示还是隐藏 $("#id").is(':visible');   //true为显示,false为隐藏 $("#id") ...

  9. Deep learning:四十三(用Hessian Free方法训练Deep Network)

    目前,深度网络(Deep Nets)权值训练的主流方法还是梯度下降法(结合BP算法),当然在此之前可以用无监督的方法(比如说RBM,Autoencoder)来预训练参数的权值,而梯度下降法应用在深度网 ...

  10. 做一个会PS切图的前端开发

    系列链接 做一个会使用PS的前端开发 做一个会PS切图的前端开发 切图方法分类 PhotoShop从CS版本演变到现在的CC版本,切图功能发生了比较大的变化,我们可以把PhotoShop CS版本时的 ...