为什么程序员“爱上”函数式编程?函数式编程已有60年的历史,但迄今为止,它的规模还比较小。虽然像谷歌这样的大公司依赖函数式编程的关键概念,但普通程序员对它们几乎一无所知。这即将改变。不仅Java或Python等语言越来越多地采用函数式编程概念,Haskell等较新的语言也在全面实现函数式编程。简单来说,函数式编程就是为不可变变量构建函数。相比之下,面向对象编程有一套相对固定的功能,用户主要修改或添加新的变量。由于函数式编程的性质,它非常适合要求苛刻的任务,例如数据分析和机器学习。但这并不意味着用户必须告别面向对象编程而完全转向函数式编程。但是用户需要了解他们的基础知识,以便在适当的时候利用它们来发挥自己的优势。一切都是为了消除副作用要理解函数式编程,您首先需要理解函数。函数是将输入转换为输出的东西,它并不总是那么简单。我们来看一个Python中的函数:defsquare(x):returnx*x这个函数很简单。它接受一个变量x,可以是int、float或double,并输出该变量的平方。现在考虑这个函数:lobal_list=[]defappend_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]而不是空列表。尽管我们没有明确说明这一点,但这表明列表确实是函数的输入。这种歧义会导致问题。来源:GitHubisnotfaithfultofunctions这些隐式输入,或其他情况下的输出,有一个正式名称:副作用。虽然本文是一个简单的示例,但在更复杂的程序中,这些副作用可能会造成真正的困难。想想如何测试append_to_list:用户不仅需要阅读第一行并使用任意x测试函数,还需要阅读整个定义,理解它的作用,定义global_list并以这种方式测试它。在处理具有数千行代码的程序时,本示例中的简单内容很快就会变得乏味。有一个简单的解决方法:坚持函数认为输入的内容。newlist=[]defappend_to_list2(x,some_list):some_list.append(x)append_to_list2(1,newlist)append_to_list2(2,newlist)newlist变化不大。输出仍然是[1,2],其他一切都保持不变。但有一件事发生了变化:代码现在没有副作用。现在,在查看函数声明时,用户可以确切地知道发生了什么。因此,如果程序运行不正常,用户还可以轻松地单独测试每个功能并查明哪个功能有问题。函数式编程就是编写纯函数。没有副作用的函数是纯函数。函数式编程的一个非常简单的定义:只使用纯函数编写程序。纯函数从不修改变量,而只是创建新变量作为输出。(作者在上面的例子中有点“作弊”:它遵循函数式编程的原则,但仍然使用全局列表。用户可以找到更好的例子,但这只是基本原则。)另外,对于给定的输入A可以得到特定输出的纯函数。相反,不纯函数依赖于一些全局变量。因此,如果全局变量不同,相同的输入变量可能会导致不同的输出。不纯的函数会使调试和维护代码变得更加困难。有一个小技巧可以更容易地发现副作用:因为每个函数都必须有某种输入和输出,所以没有任何输入或输出的函数声明一定是不纯的。如果您进行函数式编程,这些可能是您需要的第一个更改语句。来源:Unsplash函数式编程不仅仅是Map和reduce函数式编程,它不包含循环结构(Loops),请看Python中的以下循环:integers=[1,2,3,4,5,6]odd_ints=[]squared_odds=[]total=0foriinintegers:ifi%2==1odd_ints.append(i)foriinodd_ints:squared_odds.append(i*i)foriinsquared_odds:total+=i与我们要进行的简单操作相比,上面的代码显然太长了。而且由于修改了全局变量,它的效率也不够高。我们可以改用下面的代码:fromfunctoolsimportreduceintegers=[1,2,3,4,5,6]odd_ints=filter(lambdan:n%2==1,integers)squared_odds=map(lambdan:n*n,odd_ints)total=reduce(lambdaacc,n:acc+n,squared_odds)这是完整的函数。它更短更快,因为它不需要遍历数组的许多元素。此外,一旦理解了过滤、映射和归约的工作原理,代码就很容易理解了。但这并不意味着所有函数式代码都使用map、reduce等。这也不意味着您需要求助于函数式编程来理解map和reduce,这些函数只是在抽象循环时弹出很多。Lambda函数:说起函数式编程的历史,很多人都会首先提到lambda函数的发明。虽然,lambda无疑是函数式编程的基石,但这并不是根本原因。Lambda函数是使程序运行的工具。然而,lambdas也可以用于面向对象的编程。静态类型:上面的例子不是静态类型,而是函数式的。尽管静态类型为代码增加了额外的安全层,但它不一定是功能性的,但它可以锦上添花。有些语言对函数式编程更友好来源:unsplash(1)PerlPerl处理副作用的方法与大多数编程语言有很大不同。它包含一个魔法参数$_,这使得处理副作用成为Perl的核心特性之一。虽然Perl确实有其优势,但作者并没有尝试将其用于函数式编程。(2)Java如果你想用Java编写函数式代码,那只能求多福了。因为不仅程序的一半会是static关键字,大多数其他Java开发者也会认为这个程序是一种耻辱。(3)ScalaScala是一种非常有趣的语言:它的目标是统一面向对象和函数式编程。许多人觉得这很奇怪,因为函数式编程旨在完全消除副作用,而面向对象编程则试图将副作用保留在对象内部。话虽如此,许多开发人员将Scala视为一种可以帮助他们从面向对象编程过渡到函数式编程的语言,这可能会帮助他们在未来几年更轻松地过渡到函数式编程。(4)PythonPython积极鼓励使用函数式编程。每个函数在默认情况下至少有一个输入self这一事实证明了这一点。这就像Python的禅宗:显式优于隐式!(5)Clojure根据其创建者的说法,Clojure80%是功能性的。默认情况下,按照函数式编程的要求,它的所有值都是不可变的。然而,这个问题可以通过为这些不可变值使用可变包装类来解决。当打开这样的包装器类时,可变值再次变为不可变值。(6)Haskell这是为数不多的纯函数式静态类型语言之一。虽然在开发过程中可能会花费大量时间,但当您调试程序时,这些努力会得到巨大回报。它不像其他语言那样容易学习,但绝对值得花时间学习。来源:unsplash与面向对象编程相比,函数式编程仍然小众。但是,如果将函数式编程原则添加到Python和其他语言中意味着什么,那就是函数式编程正在获得牵引力。这是非常有道理的:函数式编程非常适合大型数据库、并行编程和机器学习。而在过去的十年里,这些迎来了繁荣。在面向对象编程具有不可估量的优势的同时,函数式代码的优势也不容忽视。只需学习一些基础知识就足以将用户转变为开发人员并为未来做好准备。
