函数式编程是一种编程范式,它将计算视为数学函数的评估,并避免状态和可变数据。这种范式与数学理论有着密切的联系,使得它在解决复杂问题时更加直观和有效。本文将探讨函数式编程的优势,与过程式编程的比较,以及F#等流行函数式语言为何不是纯粹的函数式编程。
函数式编程与数学的紧密联系使其在解决问题时更加直观。在函数式编程中,问题可以通过一系列数学函数来解决,这些函数具有明确的输入和输出,而不需要考虑程序的状态。这种特性使得函数式编程在处理并发和并行计算时更加高效。
如果有数学背景,会发现计算机科学中的许多理论都建立在数学基础之上。尽管生活在快速应用开发的潮流中,核心和重复的任务被抽象化,但仍然在这些抽象之上寻找现实世界问题的解决方案。无论如何,问题的解决都基于数学。函数式编程更符合数学逻辑,它使用一套去符号语义(可以在本文的第一部分看到一些简单的例子),使得能够以比过程式语言更简单、更有效的方式解决问题。这种去符号语义使免于非终止的循环语句、goto和break语句。
函数式语言的设计使其模仿数学概念,因此表达式中的子表达式可以以任何顺序进行求值。这一特性使得函数式语言成为并行编程的首选。
在本文的第一部分代码4中,可以看到函数square需要一个函数作为参数,具有以下去符号语义:
let square (f : int -> int) n = f(n) * f(n);;
像这样,所有类型和函数都明确地参数化了需要给出什么和将返回什么。这一特性使得代码更加通用,意味着可以将功能应用于不同的相关类型。
众所周知,阶乘用于比较解决特定问题的算法性能,值越小性能越高。与典型的过程式编程相比,理论上函数式编程中没有赋值操作。(参见本文第一部分的例子)。过程式程序包含90%的赋值语句。这是它们具有更高O(n)的主要原因。
面向对象编程允许进行模块化编程,但是参数化类型和非顺序属性的补充使得使用函数式语言编写真正的模块化编程成为可能。可以在本文第一部分的代码4中看到,函数以模块化的方式被消费。
令人惊讶的是,大多数过程式语言都具有一些函数式行为。函数式语言在何处超越过程式编程?让看看。
像C、C++这样的过程式语言具有函数指针,可以将它们视为高阶和柯里化函数。然而,这些函数指针非常有限,在这些语言中,不能在没有任何名称的情况下动态创建函数。
这些过程式语言也支持递归。但是,这些又是通过顺序语句处理的,就像人类接近计算机一样。
由于它更符合数学,函数式编程的概念基于数学的Lambda演算。它是一个系统,用于以更有效、更简单的方式表达任何类型的算法。通常,在数学和过程式语言中,函数需要通过任意名称来定义和引用,例如:
f(x) = { 0 if x = 0
x^2 * sin(1/x^2) if x ≠ 0
但在高阶函数中,对命名的坚持是不一致的。例如:
定义x和y分别为x=2和y=4,那么可以说xx=y,在这种情况下,不需要给这个函数任何名称。
Lambda演算中可以匿名定义函数,表达函数对其参数的作用。Lambda演算提供了一个表示法?来定义函数。例如,f(x) = x + 1可以定义为?x.x+1。在这里,函数的名称和参数是不重要的,这个函数的应用f(2)将是(?x.x+1)2。在这个例子中,?是函数,x是参数,x+1是函数体。
如果t是Lambda演算,如果:
t = x where x is a variable
t = ?x.M where x is a variable and M is a lambda expression
t = MN where M and N are lambda expressions
函数应用是左结合的,例如f x y = (f(x))(y)。简单地说,一个Lambda演算只接受一个参数并应用它,然后返回一个结果。所有函数式语言的行为(递归、任意顺序演化、无状态)实际上是Lambda演算的基本行为。
在Lambda演算中存在两种类型的变量:
例如,在?x.xy中,x是绑定变量,y是自由变量。在编程语言中,分别称这些为参数或参数和局部变量。
有两个直接原因: