编程|程序员为何与函数式编程“坠入爱河”?( 二 )


append_to_list2(2newlist)
newlist
它并没有做出太大的改变 。 输出仍然是[12
, 并且其他所有内容也保持不变 。 但是有一样改变了:该代码现在摆脱了副作用 。
现在 , 当查看函数声明时 , 用户能确切地知道发生了什么 。 因此 , 如果程序运行不正常 , 用户也可以轻而易举地单独测试每个功能 , 并查明哪个功能有问题
函数式编程正在编写纯函数
没有副作用的函数是指其输入和输出都具有明确的声明 , 而没有副作用的功能就是纯函数 。
函数式编程一个非常简单的定义:仅用纯函数编写程序 。 纯函数永远不会修改变量 , 而只会创建新的变量作为输出 。 (笔者在上面的示例中稍微“作弊”了一下:它遵循函数式编程的原则 , 但仍使用全局列表 。 用户可以找到更好的示例 , 但这只是基本原则 。 )
此外 , 对于给定输入的纯函数 , 可以得到特定的输出 。 相反 , 不纯函数则依赖于一些全局变量 。 因此 , 如果全局变量不同 , 则相同的输入变量可能导致不同的输出 。 不纯函数会使代码的调试和维护变得更加困难 。
有一个更容易发现副作用的小窍门:由于每个函数都必须具有某种输入和输出 , 因此没有任何输入或输出的函数声明一定是不纯的 。 如果采用函数式编程 , 这些则可能是第一批需要的更改声明 。
图源:unsplash
函数式编程不仅只有Map和reduce函数式编程中不包含循环结构(Loops) , 请看下面这些Python中的循环:
integers = [123456

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 = [123456

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
Perl
Perl对于副作用的处理方法与大多数编程语言截然不同 。 它包含一个神奇的参数 $_ , 这使得处理副作用成为Perl核心功能之一 。 尽管Perl确实有其优点 , 但作者不会尝试使用它进行函数式编程 。
Java
如果要用Java编写函数式代码的话 , 只能自求多福了 。 因为该程序的一半不仅将都是static 关键字 , 而且其他大多数Java开发人员也会将此程序视为耻辱 。
Scala
Scala是一个很有趣的语言:它的目标是统一面向对象和函数式编程 。 很多人都觉得这很奇怪 , 因为函数式编程旨在彻底消除副作用 , 而面向对象的编程则试图将副作用保留在对象内部 。


推荐阅读