Scala之偏函数Partial Function
https://blog.csdn.net/bluishglc/article/details/50995939
从使用case语句构造匿名函数谈起
在Scala里,我们可以使用case语句来创建一个匿名函数(函数字面量),这有别于一般的匿名函数创建方法。来看个例子:
scala> List(1,2,3) map {case i:Int=>i+1}
res1: List[Int] = List(2, 3, 4)
这很有趣,case i:Int=>i+1构建的匿名函数等同于(i:Int)=>i+1,也就是下面这个样子:
scala> List(1,2,3) map {(i:Int)=>i+1}
res2: List[Int] = List(2, 3, 4)
《Scala In Programming》一书对独立的case语句作为匿名函数(函数字面量)有权威的解释:
Essentially, a case sequence is a function literal, only more general. Instead of having a single entry point and list of parameters, a case sequence has multiple entry points, each with their own list of parameters. Each case is an entry point to the function, and the parameters are specified with the pattern.
1
一个case语句就是一个独立的匿名函数,如果有一组case语句的话,从效果上看,构建出的这个匿名函数会有多种不同的参数列表,每一个case对应一种参数列表,参数是case后面的变量声明,其值是通过模式匹配赋予的。
使用case语句构造匿名函数的“额外”好处
使用case语句构造匿名函数是有“额外”好处的,这个“好处”在下面这个例子中得到了充分的体现:
List(1, 3, 5, "seven") map { case i: Int => i + 1 } // won't work
// scala.MatchError: seven (of class java.lang.String)
List(1, 3, 5, "seven") collect { case i: Int => i + 1 }
// verify
assert(List(2, 4, 6) == (List(1, 3, 5, "seven") collect { case i: Int => i + 1 }))
在这个例子中:传递给map的case语句构建的是一个普通的匿名函数,在把这个函数适用于”seven”元素时发生了类型匹配错误。而对于collect,它声明接受的是一个偏函数:PartialFunction,传递的case语句能良好的工作说明这个case语句被编译器自动编译成了一个PartialFunction!这就是case语句“额外”的好处:case语句(组合)除了可以被编译为匿名函数(类型是FunctionX,在Scala里,所有的函数字面量都是一个对象,这个对象的类型是FunctionX),还可以非常方便的编译为一个偏函数PartialFunction!(注意:PartialFunction同时是Function1的子类)编译器会根据调用处的函数类型声明自动帮我们判定如何编译这个case语句(组合)。
上面我们直接抛出了偏函数的概念,这会让人头晕,我们可以只从collect这个示例的效果上去理解偏函数:它只对会作用于指定类型的参数或指定范围值的参数实施计算,超出它的界定范围之外的参数类型和值它会忽略(未必会忽略,这取决于你打算怎样处理)。就像上面例子中一样,case i: Int => i + 1只声明了对Int参数的处理,在遇到”seven”元素时,不在偏函数的适用范围内,所以这个元素被忽略了。
正式认识偏函数Partial Function
如同在一开始的例子中那样,我们手动实现了一个与case i:Int=>i+1等价的那个匿名函数(i:Int)=>i+1,那么在上面的collect方法中使用到的case i: Int => i + 1它的等价函数是什么呢?显然,不可能是(i:Int)=>i+1了,因为我们已经解释了,collect接受的参数类型是PartialFunction[Any,Int],而不是(Int)=>Int。 那个case语句对应的偏函数具体是什么样的呢?来看:
scala> val inc = new PartialFunction[Any, Int] {
| def apply(any: Any) = any.asInstanceOf[Int]+1
| def isDefinedAt(any: Any) = if (any.isInstanceOf[Int]) true else false
| }
inc: PartialFunction[Any,Int] = <function1>
scala> List(1, 3, 5, "seven") collect inc
res4: List[Int] = List(2, 4, 6)
PartialFunction特质规定了两个要实现的方法:apply和isDefinedAt,isDefinedAt用来告知调用方这个偏函数接受参数的范围,可以是类型也可以是值,在我们这个例子中我们要求这个inc函数只处理Int型的数据。apply方法用来描述对已接受的值如何处理,在我们这个例子中,我们只是简单的把值+1,注意,非Int型的值已被isDefinedAt方法过滤掉了,所以不用担心类型转换的问题。
上面这个例子写起来真得非常笨拙,和前面的case语句方式比起来真是差太多了。这个例子从反面展示了:通过case语句组合去是实现一个偏函数是多么简洁。实际上case语句组合与偏函数的用意是高度贴合的,所以使用case语句组合是最简单明智的选择,同样是上面的inc函数,换成case去写如下:
scala> def inc: PartialFunction[Any, Int] =
| { case i: Int => i + 1 }
inc: PartialFunction[Any,Int]
scala> List(1, 3, 5, "seven") collect inc
res5: List[Int] = List(2, 4, 6)
当然,如果偏函数的逻辑非常复杂,可能通过定义一个专门的类并继承PartialFunction是更好选择。
Case语句是如何被编译成偏函数的
关于这个问题在《Programming In Scala》中有较为详细的解释。对于这样一个使用case写在的偏函数:
val second: PartialFunction[List[Int],Int] = {
case x :: y :: _ => y
}
In fact, such an expression gets ranslated by the Scala compiler to a partial function by translating the patterns twice—once for the implementation of the real function, and once to test whether the function is defined or not. For instance, the function literal { case x :: y :: _ => y }above gets translated to the following partialfunction value:
new PartialFunction[List[Int], Int] {
def apply(xs: List[Int]) = xs match {
case x :: y :: _ => y
}
def isDefinedAt(xs: List[Int]) = xs match {
case x :: y :: _ => true
case _ => false
}
}
为什么偏函数需要抽象成一个专门的Trait
首先,在Scala里,一切皆对象,函数字面量(匿名函数)也不例外!这也是为什么我们可以把函数字面量赋给一个变量的原因, 是对象就有对应的类型,那么一个函数字面量的真实类型是什么呢?看下面这个例子:
scala> var inc = (x: Int) => x + 1
inc: Int => Int = <function1>
scala> inc.isInstanceOf[Function1[Int,Int]]
res0: Boolean = true
在Scala的scala包里,有一系列Function trait,它们实际上就是函数字面量作为“对象”存在时对应的类型。Function类型有多个版本,Function0表示无参数函数,Function1表示只有一个参数的函数,以此类推。至此我们解释的是一个普遍性问题:是函数就是对象,是对象就有类型。那么,接下来我们看一下偏函数又应该是什么样的一种“类型”?
从语义上讲,偏函数区别于普通函数的唯一特征就是:偏函数会自主地告诉调用方它的处理参数的范围,范围既可是值也可以是类型。针对这样的场景,我们需要给函数安插一种明确的“标识”,告诉编译器:这个函数具有这种特征。所以特质PartialFunction就被创建出来用于“标记”这类函数的,这个特质最主要的方法就是isDefinedAt!同时你也记得PartialFunction还是Function1的子类,所以它也要有apply方法,这是非常自然的,偏函数本身首先是一个函数嘛。
从另一个角度思考,偏函数的逻辑是可以通过普通函数去实现的,只是偏函数是更为优雅的一种方式,同时偏函数特质PartialFunction的存在对调用方和实现方都是一种语义更加丰富的约定,比如collect方法声明使用一个偏函数就暗含着它不太可能对每一个元素进行操作,它的返回结果仅仅是针对偏函数“感兴趣”的元素计算出来的
为什么偏函数只能有一个参数?
为什么只有针对单一参数的偏函数,而不是像Function特质那样,拥有多个版本的PartialFunction呢?在刚刚接触偏函数时,这也让我感到费解,但看透了偏函数的实质之后就会觉得很合理了。我们说所谓的偏函数本质上是由多个case语句组成的针对每一种可能的参数分别进行处理的一种“结构较为特殊”的函数,那特殊在什么地方呢?对,就是case语句,前面我们提到,case语句声明的变量就是偏函数的参数,既然case语句只能声明一个变量,那么偏函数受限于此,也只能有一个参数!说到底,类型PartialFunction无非是为由一组case语句描述的函数字面量提供一个类型描述而已,case语句只接受一个参数,则偏函数的类型声明自然就只有一个参数。
但是,上这并不会对编程造成什么阻碍,如果你想给一个偏函数传递多个参数,完全可以把这些参数封装成一个Tuple传递过去!
Scala之偏函数Partial Function的更多相关文章
- python函数编程-偏函数partial function
一般的,通过设定函数参数的默认值,可以减低函数调用的难度.比如:int()函数可以把字符串转换成整数: >>> int(') 123 事实上,int()函数还提供额外的base参数, ...
- Scala Partial Function从官方文档解析
A partial function of type PartialFunction[A, B] is a unary function where the domain does not neces ...
- Python partial function 偏函数
Partial function 偏函数是将所要承载的函数作为partial()函数的第一个参数,原函数的各个参数依次作为partial()函数后续的参数,除非使用关键字参数. 当函数的参数个数太多, ...
- python中的偏函数partial
Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function).要注意,这里的偏函数和数学意义上的偏函数不一样. 在介绍函数参数的时候,我们讲到,通过 ...
- iter的特殊用法以及偏函数partial
iter()的特殊用法 常规使用 iter(obj),会返现一个迭代器,如果 obj 不是可迭代对象,则会报错. 特殊用法(哨兵模式) iter(object[, sentinel]) sentine ...
- partial function
[partial function] functools.partial(func[,*args][, **keywords]) Return a new partial object which w ...
- [React] Pass Data To Event Handlers with Partial Function Application
In this lesson we’ll see how to pass an item’s id value in an event handler and get the state to ref ...
- python进阶(17)偏函数partial
什么是偏函数partial python中提供一种对于函数固定属性的函数 偏函数的作用 把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数 偏函数的语法 使用偏函数必须先导入from ...
- python3 第二十三章 - 函数式编程之Partial function(偏函数)
要注意,这里的偏函数和数学意义上的偏函数不一样,偏函数是2.5版本以后引进来的东西,属于函数式编程的一部分.前面章节中我们讲到,通过设定参数的默认值,可以降低函数调用的难度.而偏函数也可以做到这一点. ...
随机推荐
- 微信小程序图表插件 - wx-charts
微信小程序图表插件(wx-charts)基于canvas绘制,体积小巧支持图表类型饼图.线图.柱状图 .区域图等图表图形绘制,目前wx-charts是微信小程序图表插件中比较强大好使的一个. wx-c ...
- SharePoint Framework 配置你的SharePoint客户端web部件开发环境
博客地址:http://blog.csdn.net/FoxDave 你可以使用Visual Studio或者是你自己的开发环境来构建SharePoint客户端web部件.你可以使用Mac.PC或是 ...
- perl代码调试
perl调试教程 一.DESCRIPTIONA (very) lightweight introduction in the use of the perl debugger, and a point ...
- 通过 onclick = "test()"事件定义的事件 , 如何触发.
<div onclick="test()" id="xxx">点击</div> function test() { alert('123 ...
- php优秀框架codeigniter学习系列——安装,配置
下载 可在官网下载,我使用的是CodeIgniter-3.1.7. 目录 打开程序目录,可看到目录结构. 我这里做一点小的修改,新建了一个 index 目录,将 index.php 和一些静态文件放入 ...
- Vuejs 用$emit 与 $on 来进行兄弟组件之间的数据传输
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- python selenium爬取QQ空间方法
from selenium import webdriver import time # 打开浏览器 dr = webdriver.Chrome() # 打开某个网址 dr.get('https:// ...
- HDU 1166 敌兵布阵(线段树点更新区间求和裸题)
Problem Description C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任 ...
- python3:logging模块 输出日志到文件
python自动化测试脚本运行后,想要将日志保存到某个特定文件,使用python的logging模块实现 参考代码: import logging def initLogging(logFilenam ...
- hadoop day 3
1.map:局部处理:reduce:汇总 mapper对数据做切分,一份程序在不同的DataNode上独立运行对数据进行处理,reduce程序将所有DataNode上的统计数据进行汇总 Mapper& ...