函数式编程的字面意思是“用函数编程”。在这里,我们谈论的是mathematical function,而不是subroutine。有什么区别?
-
这是一个函数(Ackermann function):
数学函数是纯的(即它没有副作用)。数学函数只做一件事:它将输入值映射到输出值。它不会改变任何变量的值。
-
这是一个计算阿克曼函数结果的子程序:
function A(m, n) {
var stack = [], exit;
do {
if (m > 0) {
m = m - 1;
while (n > 0) {
stack.push(m);
n = n - 1;
}
n = 1;
} else {
m = stack.pop();
n = n + 1;
}
} while (m !== exit);
return n;
}
子程序可能是纯的,也可能不是纯的。例如,上面的子程序是不纯的,因为它修改了变量m、n和stack。因此,虽然它计算的是阿克曼函数的结果,它是一个数学函数,但它不是一个数学函数。
现在,考虑您的 counter 函数:
function counter(count) {
return function () {
return ++count;
};
}
var countup = counter(0);
alert(countup()); // 1
alert(countup()); // 2
alert(countup()); // 3
这是函数式编程吗?简短的回答是,这是有争议的,因为您确实在使用higher-order functions。但是,您的 counter 函数不是数学函数。因此,在函数式编程(即使用数学函数编程)的严格定义中,您的程序并不是真正的函数式。
注意:我认为大部分混淆的产生是因为在 JavaScript 中,一流的子例程被称为函数。实际上,它们可以用作函数。但是,它们不是数学函数。
实际上,您的程序是面向对象的。每次调用 counter 时,您都在创建一个新的 abstract data type 代表计数器对象。由于该对象只定义了一个操作,因此您可以从counter 函数中返回该操作本身。因此,您的直觉是绝对正确的。这不是函数式编程。这是面向对象的编程。
那么您将如何使用函数式编程来实现计数器?
在函数式编程中,一切都可以定义为函数。那很奇怪。数字呢?好吧,numbers can be defined as functions too。 一切都可以定义为一个函数。
然而,为了简单起见,让我们假设我们有一些原始数据类型,例如Number,并且我们可以使用structural typing 定义新的数据类型。例如,任何具有{ count: Number } 结构的对象(即任何具有名为count 的单一属性的对象,其类型为Number)都是Counter 类型的值。例如,考虑:
var counter = { count: 5 }; // counter :: Counter
打字判断counter :: Counter读作“counter是Counter类型的值”。
但是,通常最好编写一个构造函数来构造新的数据结构:
// Counter :: Number -> Counter
function Counter(count) {
return { count: count };
}
var counter = Counter(5); // counter :: Counter
注意Counter的值(它是Number -> Counter类型的函数,读作“Number到Counter”)不同于Counter类型(它是形成{ count: Number })。类型和值可以具有相同的名称。我们知道它们是不同的。
现在,让我们编写一个返回计数器值的函数:
// count :: Counter -> Number
function count(counter) {
return counter.count;
}
这不是很有趣。然而,这是因为Counter 数据类型本身并不是很有趣。事实上,Number 数据类型和Counter 数据类型是isomorphic(即我们可以使用函数Counter 将任何数字n 转换为等效的计数器c,我们可以将计数器转换为@使用函数count将987654365@变回数字n,反之亦然。
因此,我们本可以避免定义 Counter 数据类型并使用 Number 本身作为计数器。但是,出于教学目的,让我们为 Counter 使用单独的数据类型。
所以,现在我们要使用名为increment 的函数来更新counter 的值。坚持,稍等。函数不能改变函数式编程中变量的值。我们如何更新counter 的值?好吧,我们无法更新counter 的值。但是,我们可以返回一个带有更新值的新计数器。这正是我们在函数式编程中所做的:
// increment :: Counter -> Counter
function increment(counter) {
return Counter(count(counter) + 1);
}
同样,我们可以定义decrement函数:
// decrement :: Counter -> Counter
function decrement(counter) {
return Counter(count(counter) - 1);
}
请注意,如果我们使用Number 作为Counter,则计数器c 的increment 和decrement 操作将分别定义为c + 1 和c - 1。这进一步加强了我们对计数器只是一个数字的理解。
这都是花花公子,但函数式编程的意义何在?
目前,函数式编程似乎比普通编程更难。毕竟,有时你真的需要变异来编写有趣的程序。例如,您可以使用函数式编程轻松做到这一点吗?
function bicounter(count) {
return {
increment: update(+1),
decrement: update(-1)
};
function update(amount) {
return function () {
return count += amount;
};
}
}
var counter = bicounter(0);
alert(counter.increment()); // 1
alert(counter.decrement()); // 0
实际上,是的,您可以使用 State monad 做到这一点。由于需要比 JavaScript 更好的函数式编程语言,我将切换到 Haskell:
import Control.Monad.State
type Counter = Int
counter :: Counter
counter = 0
increment = modify (+1)
decrement = modify (subtract 1)
alert = get >>= (liftIO . print)
program = do
increment
alert
decrement
alert
main = evalStateT program counter
这是一个可运行的 Haskell 程序。是不是很简洁?这就是函数式编程的力量。如果您对函数式编程的想法很感兴趣,那么您绝对应该考虑learning Haskell。