程序员为什么与函数式编程“坠入爱河”?

本文转载自公众号“读芯术”(ID:AI_Discovery)。程序员

函数式编程发展至今已有60年的历史,可是截至目前,它仍然算是比较小众。尽管像Google这样的大公司依赖于函数式编程的关键概念,可是普通程序员对此几乎一无所知。数据库

这种状况即将改变了。不只是Java或Python这样的语言愈来愈多地采用了函数式编程的概念,相似Haskell这样的新语言也正在彻底实现函数式编程。编程

简单来讲,函数式编程就是为不可变变量构建函数。与之相反,面向对象的编程则是有一组相对固定的函数,而用户主要是修改或添加新变量。数组

因为函数式编程的特性,它很是适合完成诸如数据分析和机器学习之类的需求任务。可是这并不意味着用户要告别面向对象的编程,转而彻底使用函数式编程。但用户须要了解其基本原理,以便在适当的时候使用它们以发挥优点。安全

一切都是为了消除反作用app

要了解函数式编程,首先须要了解函数。函数是将输入转换为输出的东西,它并不老是这么简单。下面看一个Python中的函数:机器学习

def square(x): 
    return x*x

这个函数很简单。它须要一个变量 x,或者是一个int,又或者是float或double,而后输出该变量的平方。编程语言

如今再思考这个函数:ide

lobal_list = []def append_to_list(x): 
    global_list.append(x)

乍一看,该函数看起来像是接受了一个任意类型的变量x,而且因为没有 return 语句,它不会返回任何值。函数式编程

请等一下!若是未事先定义global_list,那么该函数将不起做用,而且在通过修改后仍输出相同的列表。尽管global_list从未被视为函数的输入,但使用函数时它也会发生改变:

append_to_list(1) 
append_to_list(2) 
global_list

它将返回[1,2]而不是一个空列表。即便咱们对此并不明确,但这代表该列表确实是该函数的输入。这种不明确可能会形成问题。

程序员为什么与函数式编程“坠入爱河”?

图源:GitHub

不忠实于函数

这些隐含的输入,或在其余状况下的输出,有一个官方的名称:side effects(反作用)。虽然本文所举的只是一个简单的示例,可是在更复杂的程序中,这些反作用可能会致使真正的困难。

请思考一下如何测试append_to_list:用户不只须要阅读第一行并使用任意的x来测试函数,还须要阅读整个定义,理解其做用,定义global_list而且以这种方式进行测试。当须要处理带有数千行代码的程序时,此示例中的简单操做可能很快就会变得乏味无趣。

有一个简单的解决方法:忠于函数认定为输入的内容。

newlist = []def append_to_list2(x, some_list): 
   some_list.append(x)append_to_list2(1,newlist) 
append_to_list2(2,newlist) 
newlist

它并无作出太大的改变。输出仍然是[1,2],而且其余全部内容也保持不变。可是有同样改变了:该代码如今摆脱了反作用。

如今,当查看函数声明时,用户能确切地知道发生了什么。所以,若是程序运行不正常,用户也能够垂手可得地单独测试每一个功能,并查明哪一个功能有问题

函数式编程正在编写纯函数

没有反作用的函数是指其输入和输出都具备明确的声明,而没有反作用的功能就是纯函数。

函数式编程一个很是简单的定义:仅用纯函数编写程序。纯函数永远不会修改变量,而只会建立新的变量做为输出。(笔者在上面的示例中稍微“做弊”了一下:它遵循函数式编程的原则,但仍使用全局列表。用户能够找到更好的示例,但这只是基本原则。)

此外,对于给定输入的纯函数,能够获得特定的输出。相反,不纯函数则依赖于一些全局变量。所以,若是全局变量不一样,则相同的输入变量可能致使不一样的输出。不纯函数会使代码的调试和维护变得更加困难。

有一个更容易发现反作用的小窍门:因为每一个函数都必须具备某种输入和输出,所以没有任何输入或输出的函数声明必定是不纯的。若是采用函数式编程,这些则多是第一批须要的更改声明。

程序员为什么与函数式编程“坠入爱河”?

图源:unsplash

函数式编程不只只有Map和reduce

函数式编程中不包含循环结构(Loops),请看下面这些Python中的循环:

integers = [1,2,3,4,5,6] 
odd_ints = [] 
squared_odds = [] 
total = 0for i in integers: 
    if i%2 ==1 
        odd_ints.append(i)for i inodd_ints: 
    squared_odds.append(i*i)for i insquared_odds: 
    total += i

相较于咱们要执行的简单操做,以上代码明显过长。并且因为修改全局变量,它也不够有效。咱们能够用如下代码替代:

from functools import reduceintegers = [1,2,3,4,5,6] 
odd_ints = filter(lambda n: n % 2 == 1, integers) 
squared_odds = map(lambda n: n * n, odd_ints) 
total = reduce(lambda acc, n: acc + n, squared_odds)

这是完整的函数。由于不须要迭代一个数组的许多元素,因此它更短也更快。并且,一旦了解了 filter、map和reduce 如何工做,代码也就容易理解了。但这并不意味着全部函数代码都使用map、reduce 等。这也不意味着须要借助函数式编程来理解map 和 reduce,这些函数只是在抽象循环时弹出不少。

  • Lambda functions:谈及函数式编程的发展史时,许多人都会先说起lambda函数的发明。尽管,lambda毫无疑问是函数式编程的基石,但这并非根本缘由。Lambda函数是可以使程序发挥做用的工具。可是,lambda也可用于面向对象的编程。

  • Static typing:上面的示例不属于静态输入,而是函数式的。即便静态类型为代码增长了一层额外的安全保护,但也并不是必定要其函数化,不过这可能会是锦上添花。

一些语言对函数式编程更加友好

程序员为什么与函数式编程“坠入爱河”?

图源:unsplash

(1) Perl

Perl对于反作用的处理方法与大多数编程语言大相径庭。它包含一个神奇的参数 $_,这使得处理反作用成为Perl核心功能之一。尽管Perl确实有其优势,但做者不会尝试使用它进行函数式编程。

(2) Java

若是要用Java编写函数式代码的话,只能自求多福了。由于该程序的一半不只将都是static 关键字,并且其余大多数Java开发人员也会将此程序视为耻辱。

(3) Scala

Scala是一个颇有趣的语言:它的目标是统一面向对象和函数式编程。不少人都以为这很奇怪,由于函数式编程旨在完全消除反作用,而面向对象的编程则试图将反作用保留在对象内部。

话虽如此,许多开发人员将Scala视为一种能够帮助他们从面向对象编程过渡到函数式编程语言,这可能会帮助他们在将来几年更容易彻底过渡到函数式编程。

(4) Python

Python积极鼓励使用函数式编程。下列事实证实了这一点:每一个函数在默认状况下都有至少有一个输入self。这就像是Python之禅:显式比隐式好!

(5) Clojure

根据其建立者的说法,Clojure的函数化达到80%。默认状况下,正如在函数式编程中所须要的,它的全部值都是不可变的。可是,能够经过对这些不可变值使用可变值包装类来解决此问题。当打开这样的包装类,可变值将再次不可变。

(6) Haskell

这是极少数纯函数式和静态类型的语言之一。尽管在开发过程当中可能会耗费大量时间,但在调试程序时这些付出都会得到巨大回报。它不像其余语言那样容易学习,可是绝对值得花时间学习。

程序员为什么与函数式编程“坠入爱河”?

图源:unsplash

与面向对象的编程相比,函数式编程仍然小众。可是,若是说在Python和其余语言中加入函数式编程原理意味着什么的话,那就是函数式编程正愈来愈受到关注。这彻底说得通:函数式编程对于大型数据库、并行编程和机器学习大有裨益。而在过去十年间,这些迎来了蓬勃发展。

虽然面向对象编程有着不可估量的优势,但函数代码的优势也不容忽视。只须要学习一些基本原理,就足以让用户成为一名开发人员,并为将来作好准备。

【责任编辑:赵宁宁 TEL:(010)68476606】