【Scala】Scala函数式编程初探

函数式编程

函数式编程是种编程典范,它将电脑运算视为函数的计算。函数编程语言最重要的基础是 λ 演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。和过程化编程相比,函数式编程里,函数的计算可随时调用。

命令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取,存储指令),表达式(内存引用和算术运算)和控制语句(跳转指令),一句话,命令式程序就是一个冯诺依曼机的指令序列。

而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,一句话,函数式程序就是一个表达式。

面向对象 vs 面向函数

面向对象:

1.数据和对数据的操作紧紧耦合

2. 对象隐藏它们操作的实现细节,其他对象调用这些操作只需要通过接口。

3. 核心抽象模型是数据自己

4. 核心活动是组合新对象和拓展已经存在的对象,这是通过加入新的方法实现的。

函数编程:

  1. 数据与函数是松耦合的
  2. 函数隐藏了它们的实现,语言的抽象是函数,以及将函数组合起来表达。
  3. 核心抽象模型是函数,不是数据结构
  4. 核心活动是编写新的函数。
  5. 变量缺省是不变的,减少可变性变量的使用,并发性好

Scala是函数式的

Scala是一种成熟的函数式语言。但,Scala不强迫使用函数式的风格,必要情况下,可以写成指令形式,用可变数据或有副作用的方法调用。但是Scala有更好的函数式编程方式做替代,因此通常可以轻松地避免使用它们。

函数式编程中的函数这个术语不是指计算机中的函数(实际上是Subroutine),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。

函数编程的一些基本特点包括:

支持闭包和高阶函数,支持惰性计算(lazy evaluation)。使用递归作为控制流程的机制。加强了引用透明性。没有副作用。

函数式编程有两种指导理念。

函数是头等结构

函数是”第一等公民”。

所谓”第一等公民”(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。

可以在函数里定义其他函数,就好像在函数里定义整数一样。还可以定义匿名函数,并随意地插入到代码的任何地方。

函数作为头等结构的这种理念简化了操作符的抽象和新控制结构的创建。这种函数的泛化具有很强的表现力,是程序保持清晰易懂的保证,而且它还在可扩展性上扮演了重要的角色。

函数编程支持函数作为第一类对象,有时称为闭包(Closure)或者仿函数(functor)对象。实质上,闭包是起函数的作用并可以像对象一样操作的对象。与此类似,FP 语言支持高阶函数(Higher-order function)。高阶函数可以用另一个函数(间接地,用一个表达式) 作为其输入参数,在某些情况下,它甚至返回一个函数作为其输出参数。这两种结构结合在一起使得可以用优雅的方式进行模块化编程,这是使用 FP 的最大好处。

方法不应有任何副作用

所谓”副作用”(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。

函数式编程强调没有”副作用”,意味着函数要保持独立,方法与其所在环境交流的唯一方式应该是获得参数和返回结果,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。

因为 FP 语言不包含任何赋值语句,变量值一旦被指派就永远不会改变。而且,调用函数只会计算出结果 ── 不会出现其他效果。因此,FP 语言没有副作用。

函数式编程的其他特性

  • 惰性计算

    FP 还引入了惰性计算(Lazy evaluation)的概念。在惰性计算中,表达式不是在绑定到变量时立即计算,而是在求值程序需要产生表达式的值时进行计算。延迟的计算使您可以编写可能潜在地生成无穷输出的函数。因为不会计算多于程序的其余部分所需要的值,所以不需要担心由无穷计算所导致的 out-of-memory 错误。一个惰性计算的例子是生成无穷 Fibonacci 列表的函数,但是对 第 n 个Fibonacci 数的计算相当于只是从可能的无穷列表中提取一项。

  • 递归

    FP 还有一个特点是用递归(recursive)做为控制流程的机制。例如,Lisp 处理的列表定义为在头元素后面有子列表,这种表示法使得它自己自然地对更小的子列表不断递归。

  • 引用透明性

    函数程序通常还加强引用透明性(Referential transparency),即如果提供同样的输入,那么函数总是返回同样的结果。就是说,表达式的值不依赖于可以改变值的全局状态。这使您可以从形式上推断程序行为,因为表达式的意义只取决于其子表达式而不是计算顺序或者其他表达式的副作用。这有助于验证正确性、简化算法,甚至有助于找出优化它的方法。

函数式编程带来的好处

函数式编程可以使得代码简洁、开发快速、代码接近自然语言易于理解等,还有有关函数式编程自身特点带来的易用性。

更方便的代码管理

函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。

易于并发编程

不变性带来的另一个好处是:由于(多个线程之间)不共享状态,不会造成资源争用(Race condition),也就不需要用锁来保护可变状态,也就不会出现死锁,这样可以更好地并发起来,尤其是在对称多处理器(SMP)架构下能够更好地利用多个处理器(核)提供的并行处理能力。

Scala函数式风格

函数式风格和指令式编程风格的代码差异,大致可以说,如果代码包含了任何var变量,那它可能就是指令式的风格。如果代码根本没有var,仅仅包含val,那它或许是函数式风格。向函数式风格转变的方式之一,就是尝试不用任何var编程。

//使用var的指令式风格
def printArgs(args: Array[String]): Unit = {
    var i = 0;
    while(i < args.length){
        println(args(i))
        i += 1
    }
}

//函数式风格
def printArgs(args: Array[String]): Unit = {
    for(arg <- args)
        println(arg)
}
//或者
def printArgs(args: Array[String]): Unit = {
    args.foreach(println)
}

上面这段代码仍然有修改的余地。重构后的printArgs方法并不是纯函数式的,因为它有副作用——打印到标准输出流

识别函数是否有副作用的地方就在于其结果类型是否为Unit。如果某个函数不返回任何有用的值,也就是说如果返回类型是Unit,那么这个函数唯一能产生的作用就只能是通过某种副作用。

函数风格的方式应该是定义对需打印的arg进行格式化的方法,不过仅返回格式化之后的字符串:

def formatArgs(args: Array[String]) = args.mkString("\n")

现在才是真正的函数式风格:完全没有副作用或var的mkString方法,能在任何可枚举的集合类型(包括数组,列表,集合映射)上调用,返回由每个数组元素调用toString,并把传入字符串做分隔符组成的字符串。

更容易测试

提倡无副作用的方法的好处之一是可以有助于你的程序更容易测试。

在上面给出了例子。任何一个有副作用的printArgs方法,你需要重定义println,捕获传递给它的输出,在检查结果。对于formatArgs来说,你可以直接检查它的返回结果:

val res = format(Array("zero","one","two"))
assert(res == "zero\none\ntwo")

这里的assert方法检查传入的Boolean表达式,如果结果为假,抛出AssertionError;否则什么也不做。

参考资料

函数式编程初探

简单λ演算

到底什么是函数式编程思维?

转载请注明作者Jason Ding及其出处

GitCafe博客主页(http://jasonding1354.gitcafe.io/)

Github博客主页(http://jasonding1354.github.io/)

CSDN博客(http://blog.csdn.net/jasonding1354)

简书主页(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)

百度搜索jasonding1354进入我的博客主页

时间: 2024-08-03 12:17:59

【Scala】Scala函数式编程初探的相关文章

函数式编程初探

[函数式编程初探] 1. 函数是"第一等公民" 所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值. 2. 只用"表达式",不用"语句" "表达式"(expression)是一个单纯的运算过程,总是有返回值:"语句"(statement)是执行某种操作,没有返回值.函数式编

[转] 函数式编程初探

诞生50多年之后,函数式编程(functional programming)开始获得越来越多的关注. 不仅最古老的函数式语言Lisp重获青春,而且新的函数式语言层出不穷,比如Erlang.clojure.Scala.F#等等.目前最当红的Python.Ruby.Javascript,对函数式编程的支持都很强,就连老牌的面向对象的Java.面向过程的PHP,都忙不迭地加入对匿名函数的支持.越来越多的迹象表明,函数式编程已经不再是学术界的最爱,开始大踏步地在业界投入实用. 也许继"面向对象编程&qu

函数式编程初探 [ 阮一峰 ]

诞生50多年之后,函数式编程(functional programming)开始获得越来越多的关注. 不仅最古老的函数式语言Lisp重获青春,而且新的函数式语言层出不穷,比如Erlang.clojure.Scala.F#等等.目前最当红的Python.Ruby.Javascript,对函数式编程的支持都很强,就连老牌的面向对象的Java.面向过程的PHP,都忙不迭地加入对匿名函数的支持.越来越多的迹象表明,函数式编程已经不再是学术界的最爱,开始大踏步地在业界投入实用. 也许继"面向对象编程&qu

第3课 Scala函数式编程彻底精通及Spark源码阅读笔记

本课内容: 1:scala中函数式编程彻底详解 2:Spark源码中的scala函数式编程 3:案例和作业 函数式编程开始: def fun1(name: String){ println(name) } //将函数名赋值给一个变量,那么这个变量就是一个函数了. val fun1_v = fun1_ 访问 fun1_v("Scala") 结果:Scala 匿名函数:参数名称用 => 指向函数体 val fun2=(content: String) => println(co

Scala入门系列(九):函数式编程

引言 Scala是一门既面向对象,又面向过程的语言,Scala的函数式编程,就是Scala面向过程最好的佐证.也真是因此让Scala具备了Java所不具备的更强大的功能和特性. 而之所以Scala一直没有替代Java,一是因为Java诞生早,基于Java开发了大量知名的工程,并且最重要的是Java现在不只是一门编程语言,而是一个庞大的技术生态圈,所以未来十年内Scala也不会完全替代Java,但是Scala会在自己特有的领域大发光彩.   将函数赋值给变量 Scala中函数是一等公民,可以独立定

Python学习十一:函数式编程

这也是我第一接触函数式编程这个概念,并不知道是干嘛的?好奇心驱使下学习了一下,有了大致的了解: 函数式编程自己的理解:就跟说话一样写程序,这个程序写出来可以直白的告诉人是要干嘛的. 以下是我读到的关于函数式编程的文章的描述: 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数![1] 函数式编程的准则:不依赖于外部的数据,而且也不改变外部数据的值,而是返回一个新的值给你.[2] 函数式编程的理念:把函数当成变量来用,关注于描述问题而不是怎么实现,这样可以让代码更易

什么是函数式编程

导读 建议先阅读一下这几篇博客: 函数式编程初探 函数式编程入门教程 图解 Monad 什么是函数式编程 函数式编程中的函数指的并不是编程语言中的函数(或方法),它指的是数学意义上的函数,即映射关系(如:y = f(x)),就是 y 和 x 的对应关系. 数学上对于函数的定义是这样的:"给定一个数集 A,假设其中的元素为 x.现对 A 中的元素 x 施加对应法则 f,记作 f(x),得到另一数集 B.假设 B 中的元素为 y." 所以当我们在讨论"函数式"时,我们其

Scala 中的函数式编程基础(二)

主要来自 Scala 语言发明人 Martin Odersky 教授的 Coursera 课程 <Functional Programming Principles in Scala>. 2. Higher Order Functions 把其他函数作为参数或者作为返回值,就是 higher order functions,python 里面也可以看到这样使用的情形.在酷壳上的博客有一个例子就是将函数作为返回值. 2.1 匿名函数 在 python 里边叫 lambda 函数,常常与 map(

Scala函数式编程进阶

1 package com.dtspark.scala.basics 2 3 /** 4 * 函数式编程进阶: 5 * 1,函数和变量一样作为Scala语言的一等公民,函数可以直接赋值给变量: 6 * 2, 函数更长用的方式是匿名函数,定义的时候只需要说明输入参数的类型和函数体即可,不需要名称,但是如果你要使用的话,一般会把这个匿名函数赋值给一个变量(其实是val常量),Spark源码中大量存在这种语法,必须掌握: 7 * 3, 函数可以作为参数直接传递给函数,这极大的简化的编程的语法,为什么这