Scalaz(3)- 基础篇:函数概括化-Generalizing Functions
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的更多相关文章
- Template 基础篇-函数模板(待看
Template 基础篇-函数模板 Template所代表的泛型编程是C++语言中的重要的组成部分,我将通过几篇blog对这半年以来的学习做一个系统的总结,本文是基础篇的第一部分. Template ...
- SQL基础篇---函数及其函数配套使用的关键字
一.数值函数 知识点1 SUM 求总和 SELECT breakfast,sum(price) FROM my_foods GROUP BY breakfast ORDER BY SUM(price) ...
- 小猪猪C++笔记基础篇(六)参数传递、函数重载、函数指针、调试帮助
小猪猪C++笔记基础篇(六) ————参数传递.函数重载.函数指针.调试帮助 关键词:参数传递.函数重载.函数指针.调试帮助 因为一些事情以及自己的懒惰,大概有一个星期没有继续读书了,已经不行了,赶紧 ...
- 前端总结·基础篇·JS(三)arguments、callee、call、apply、bind及函数封装和构造函数
前端总结系列 前端总结·基础篇·CSS(一)布局 前端总结·基础篇·CSS(二)视觉 前端总结·基础篇·CSS(三)补充 前端总结·基础篇·JS(一)原型.原型链.构造函数和字符串(String) 前 ...
- HTML5和CSS3扁平化风格博客(基础篇)
多学一点总是好的~ 自始至终都觉得的css和html效果比较美观,于是在看慕课网教程时,自己也跟着敲了深爱着的前端代码 这部分分为两部分:①基础篇:http://www.imooc.com/learn ...
- Python基础篇(三)_函数及代码复用
Python基础篇_函数及代码复用 函数的定义.使用: 函数的定义:通过保留字def实现. 定义形式:def <函数名>(<参数列表>): <函数体> return ...
- PHP丨PHP基础知识之PHP基础入门——函数「理论篇」
前两天讲过PHP基础知识的判断条件和流程控制,今天来讲讲PHP基础知识之PHP基础入门--函数! 一.函数的声明与使用 1.函数名是标识符之一,只能有数字字母下划线,开头不能是数字. 函数名的命名,须 ...
- 夯实Java基础系列1:Java面向对象三大特性(基础篇)
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 [https://github.com/h2pl/Java-Tutorial](https: ...
- 《量化投资:以MATLAB为工具》连载(2)基础篇-N分钟学会MATLAB(中)
http://www.matlabsky.com/thread-43937-1-1.html <量化投资:以MATLAB为工具>连载(3)基础篇-N分钟学会MATLAB(下) ...
随机推荐
- fir.im Weekly - Swift 3.0 的迁移适配指南
无论你是移动开发者,还是桌面端开发者,或者正在IoT领域探索的技术人员,那么应该更加关注 iDev 全平台开发者大会,也许是后半年 iOS 开发者最盛大的技术盛宴.既有知名公司带来专业视野,又有从 S ...
- C++生成二级制文件过程(预处理->编译->链接 )
转载请注明出处 Windows下C++编程,通过VC生成工程,编写C++源文件,点运行,代码没问题直接出结果.VC什么都帮我们搞了,不了解其中过程也完全没问题. 转到linux下写c++,总觉得有点虚 ...
- Python数组合并
Python数组合并 a = [1, 2] b = [3, 4] c = a + b print(c) # [1, 2, 3, 4]
- Archlinux 2015.07.01 和 Windows7 双系统 安装教程
提前在windows7下给Archlinux预留一个分区,大小最好在20G以上(根据自己硬盘情况分配). 第一步,安装前的准备 从arch官网下载最新的ISO文件archlinux-2015.07.0 ...
- Github快速入门手册
最近在试用Github,开源的思想也让人觉得把一些经验分享出来是非常好的事情.附件是doc文件,如有需要请注意查收.希望能对你有帮助. GITHUB基于互联网的版本控制快速入门手册 如有不妥,欢迎指正 ...
- 基于Metronic的Bootstrap开发框架经验总结(2)--列表分页处理和插件JSTree的使用
在上篇<基于Metronic的Bootstrap开发框架经验总结(1)-框架总览及菜单模块的处理>介绍了Bootstrap开发框架的一些基础性概括,包括总体界面效果,以及布局.菜单等内容, ...
- A/B 测试之前必须要了解的 10 件事
如今,转化率优化(CRO)已是营销人员必须具备的技能,并且与 ROI 直接挂钩.但是在优化网页的转化率方面又有太多因素要考量,如果你已经不堪其忧,请专心做一件事-- A/B 测试. A/B测试,即你设 ...
- HT for Web的HTML5树组件延迟加载技术实现
HT for Web的HTML5树组件有延迟加载的功能,这个功能对于那些需要从服务器读取具有层级依赖关系数据时非常有用,需要获取数据的时候再向服务器发起请求,这样可减轻服务器压力,同时也减少了浏览器的 ...
- 纯Shading Language绘制飞机火焰效果
上篇<纯Shading Language绘制HTML5时钟>体现了GLSL可编程性特点,但没有体现GLSL可编程出各种酷炫效果的特点,今天我们将用纯Shading Language绘制火焰 ...
- 关于WEB Service&WCF&WebApi实现身份验证之WCF篇(2)
因前段时间工作变动(换了新工作)及工作较忙暂时中断了该系列文章,今天难得有点空闲时间,就继续总结WCF身份验证的其它方法.前面总结了三种方法(详见:关于WEB Service&WCF& ...