Scala中的函数式特性|Scala布道师挖财资深架构师王宏江

Posted StuQ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala中的函数式特性|Scala布道师挖财资深架构师王宏江相关的知识,希望对你有一定的参考价值。


有一篇以调侃的口气评述各种编程语言史的文章里面说到:“2003年,一个叫Martin Odersky的醉汉看见了好时瑞森花生酱杯的广告,展示了某个人的花生酱倒入另一个人的巧克力的场景,他忽然有了个点子,创造了Scala,一种结合了面向对象和函数式编程的语言。这同时激怒了两个阵营的忠实信徒,他们立刻宣布要发动圣战烧死异教徒。”


这句调侃很有意思,它折射出这两种编程风格互相格格不入,所以尝试把这两种风格结合起来也是一种开创,当然注定了一开始就会存在争议。但争议归争议,我们作为工程人员最主要的还是看重它解决问题的能力,而当前来看Scala无疑已经算是比较成功的编程语言了。


这次跟大家交流一下Scala里的一些函数式特性,关于函数式特性这个话题比较大,这次时间很有限,我不知道能聊到多少内容,尽量抛出一些能引发大家思考的东西。编程语言分很多种范式,其中大家熟悉的有命令式(也称指令式)与函数式这两种范式。而这两种编程范式追溯起来是由这两位先贤确定的:

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江

上图中,左边跑步的年轻小伙是图灵,右边年老一点的是阿隆佐邱奇。八卦一下,图灵也是个长跑健将,在学院期间经常早起先跑个半程马拉松;阿隆佐邱奇也是图灵在普林斯顿的老师,所以上世纪的普林斯顿的先贤们奠定了整个计算机领域的发展。


图灵机,以及冯诺依曼对它的实现(冯诺依曼体系),从风格上被归为“命令式”,它的运算过程可以看作不断地修改内存来反映运算的结果,即用命令修改状态;而阿隆佐邱奇提出的 Lambda 演算,则是通过连续的运算(基于函数)得到最终结果。


基于Lambda演算的被归为函数式,在众多的函数式编程语言里,Scala的血缘继承自ML 和Haskell 最多。关于ML有本非常好的书《ML for the Working Programmer》,这本书里面提到:“函数式程序是跟值打交道的,而不是跟状态打交道的。它们的工具是表达式,而不是命令。”


那么,我们先要了解一下这里说的“表达式”以及Scala里的表达式与Java有什么不同。在Scala里,所有的表达式都有值(继承自 ML)。举个例子,a = 1 是一段赋值表达式,但这个表达式有其自身的结果,它是一个Unit类型的结果,还有if else,try catch 甚至 throw Exception 等表达式,它们也都有结果。其中赋值语句的返回值问题跟 Java 里有很大的差异,我们来比较一下,Java里:


int a,b,c;
a = b = c = 1;


这样是没有问题的。


Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江

而Scala里则会出错,类型不匹配,因为c = 0这个表达式的结果是 Unit 类型,再把它赋值给 b 的时候类型不对。


特别典型的,我们在Java里逐行读取一段文本

while( (line = readLine() ) != null ) {
…
}


这里期望是每次读取一行内容赋值给line变量,并判断line不为null,在Java里没有问题,但Scala里 line = readLine() 这个赋值表达式的结果是一个 Unit 类型的值,导致永远不为null。而对于 {…} 这种代码块,也是一个表达式,它由若干表达式组合,最后一个表达式的值是它的值。先一定弄清楚这个差异,接下来我们谈函数的内容:


狭义地区分(从可传递性上)方法(method):


指的是在trait/class/object中以def关键字声明的,它不能被直接传递。函数(function)类型为ParamsType=>ResultType的变量,这些变量背后是用FuncfonN对象来封装的;可以被传递。方法可以转换为函数。


广义上,抛开背后的实现,方法就是函数,编译器某些场景自动把方法封装为一个函数对象来传递。Scala社区并不特别区分这两个名词,注意语境,有时候函数就是指方法,有时候则是指函数对象。


函数作为一等公民体现在哪儿? 这里简单介绍一些特性:

1) 可传递/赋值

2) 嵌套函数和匿名函数

3) 高阶

4) 偏应用(partial application)

5) 闭包

可传递性即函数可以像值一样被传来传去。

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
或者显式的定义一个函数值:

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
嵌套函数则是可以在函数内部再声明函数:

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江

嵌套函数应用场景并不多,其中一个场景是将递归函数的转为尾递归方式。

匿名函数或 lambda 表达式也称函数字面量 (function literal):

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
高阶函数则是接受另一个函数做参数,或者返回的而结果是一个函数:

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江

在lambda演算中,每个表达式都代表一个只有单独参数的函数,这个函数的参数本身也是一个只有单一参数的函数,同时函数的值是又一个只有单一参数的函数。


那么问题来了:多个参数的函数怎么办?

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
上面就是一个例子,把两个参数的函数通过柯里化转换为接受一个参数。它有点像一个函数链,把一个带有多个参数的函数转换为多个只有一个参数的函数来执行。

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江

柯理化的实际用途?

1) 控制抽象,可改变代码的书写风格:

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
2)实现部分应用函数。部分应用函数 (partial application function) 把一个函数适配为另一个函数,举例来看:

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
然后看一下闭包,先回顾一下Java里的匿名内部类:

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
疑问:innerVar 还是分配在栈空间上么?

那么闭包简单来说就是一段行为,以及它的上下文。

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江

这个方法返回了一个闭包(一段lambda实例),这个闭包里引用到了局部的变量localVal。闭包有很多问题都是引用环境的绑定(The Binding of Referencing Environments) 所产生的。


一个JavaScript 的例子

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
解决方式:多一层抽象

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
其实上面的解决方式里又涉及另一个话题:参数的传递方式,这里不讲了。当然闭包的绑定问题,还有高阶的话题,比如在递归情况下引用环境约束又是怎样的。

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江

这个留给大家再进一步挖掘了。


最后简单说一点类型系统里的lambda,在Scala里generic types as first-class types,在类型系统上跟Java比有非常大的差异,它支持高阶特性。


我们从多态的层面来看,除了继承/子类型方式,还有泛型/参数化类型也可以实现多态。在 Java 里的泛型是在Java5才支持的(作者也是Scala的作者Martin),所以它有一些历史包袱:比如 List<T> 这个类型,T是类型参数,在Java里只能支持普通的类型参数,在Scala里写法不同:

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江

那现在,如果对T这个参数再进一步抽象,它如果也是一个type constructor 会如何呢?

Java : class List2<C<T>> {} //不支持

Scala: class List2[C[T]]

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
不要小看这点差异,这是类型系统层面巨大的不同。它把类型的抽象层次一下提升了一个级别,对类型简单地归纳:

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
类型是对数据的抽象

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江

那对类型再抽象呢?又叫什么?

Kind: 类别,对类型的抽象

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
所有的proper type被抽象为同一种 kind,用 * 表示:

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
以函数的视角:

Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江
Scala中的函数式特性|Scala布道师、挖财资深架构师王宏江

Scala的类型系统是图灵完备的,即利用类型系统本身就可以解决一些问题。


时间比较紧,先分享这些东西。可能不太好消耗,希望这个分享对大家有所启发,对Scala的抽象有更深的理解。


问题1:请问初始学习Scala,看那些书比较好.?

先推荐这本《Scala程序设计:Java虚拟机多核编程实战 》,面向初学者。然后 Martin自己写的《Programming in Scala》内容非常充实,是最好的一本教材需要细读,现在可能有第三版了,中文版只有第一版。

还有《快学Scala》也值得一看。

最后《Scala函数式编程》这本书面向高阶特性,介绍了包括fuctor, monad 等函数式概念


问题2:C 语言工程师要想 转做 Scala 继续学习补充哪些知识呢?

对于 C 的人,转Scala的话,可能先把 Java 的特性弄熟更有利,Scala基于JVM平台,能先用起来解决实际问题,自然就能不断进步了。


问题3:Scala究竟是简单还是困难呢?和C++相比的有缺点在哪了?

如果你把Scala 当作 Java来用,并不复杂,不需要消化它的高阶特性,可以很快上手。有很多人将Scala和 C++相比,我没有C++方面的背景,很难回答。

Scala的复杂度主要是类型系统层面的,可以说是 Haskell 的影响,主要在思维方式的差异性上,对程序员有较大冲击。


问题4:Scala在程序排行榜中所占比例不太好的原因是什么呢?

很多理论都是从学院界逐渐转入工业界的,大众程序员对函数式的理解现在也不够普及,这需要一个过程。


问题5:请问在推广使用Scala方面有什么好的建议?

先解决实际问题,业务用起来了自然就推动了。


问题6:Scala的闭包和Node.js的闭包差异在什么地方呢?

在上下文绑定问题上没有差异。至于各种语言/平台,闭包在底层用什么方式来承载,我不好说。在Scala里闭包就是一个FunctionN 的类型,背后还是用对象来承载的。


问题7:命令式编程和函数式编程之间各有什么利弊?

看场景,命令式程序比较直接,容易理解。

函数式在并发场景更有用武之地。


本文整理自微课堂:Scala布道师、挖财资深架构师王宏江老师的分享内容。【ArchSummit北京2016】七月深圳,是怎样的技术触动技术的心弦?不管是共享经济下各巨头同台竞技还是大数据上中外大牛各显神通,一切都将在12月北京更进一步:立足经典,双十一技术竞技、高可用架构、大数据、移动等,精彩依旧;拥抱热点,视频直播、新闻资讯、Fin-Tech等,引爆架构前沿。更精彩的技术尽在ArchSummit北京2016,8折报名限时优惠,打开bj2016.archsummit.com查看详情!


课程推荐

StuQ 特别邀请 ThoughtWorks 资深技术顾问、Scala 老司机吴雪峰老师共同推出《用 Spark 源码学 Scala 核心编程技能》精品直播小班课。

本课程强调实践原则和应用开发快速入门,提纲挈领的介绍 Scala 的核心知识和应用开发特性, 通过专门设计的讲解和练习案例,帮助学员快速入门 Scala 应用开发,也能为以后的学习和工作提供指导意见。 

注:仅60个名额,满25人开班,名额有限,抓紧抢位。 感兴趣的同学点击“阅读原文”即可报名!



感兴趣的同学可点击“阅读原文”购买课程!

以上是关于Scala中的函数式特性|Scala布道师挖财资深架构师王宏江的主要内容,如果未能解决你的问题,请参考以下文章

案例分析丨Scala在挖财的应用实践

scala高级特性-01

第6节 Scala中的高阶函数:123

Scala的高级特性

Scala的高级特性,实操演习

Scala基础篇-函数式编程的重要特性