这可以被认为是一个纯函数(函数式编程)吗?
Can this be considered a pure function (functional programming)?
我一直在阅读函数式编程及其概念。我很清楚,在大型项目中工作时,您总是需要混合(在一定程度上)多种范例,例如 OO 和函数式。理论上,函数纯度等概念过于严格如
The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change while program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices. (https://en.wikipedia.org/wiki/Pure_function)
也就是说,这个(或可以被认为是)代码是一个纯函数吗?
const externalVar = 10;
function timesTen(value) {
return externalVar * value;
}
我问这个是因为,在这种情况下,timesTen
函数将 总是 return 相同的输入值,任何人都可以更改 externalVar
的值,因为这是一个常量。但是,这段代码打破了访问外部函数作用域的规则。
是的。保证纯净
原因是它只依赖于绑定和不可变的自由变量。
However, this code breaks the rule of accessing external function's
scope.
您的报价中没有任何内容表明您无法访问自由变量。它说外部输入是从文件、网络等读取的,而不是来自先前作用域的自由变量。
甚至 Haskell 也使用像 foldr
这样的全局函数名称,它在每个使用它的函数中都是一个自由变量,当然结果是纯的。
请记住,按名称命名的函数只是变量。 parseInt
是一个指向一个函数的变量,所以如果你应该在另一个函数中使用的每个函数都作为参数传递,那么很难做任何事情。
如果您将 parseInt
重新定义为非纯函数或在程序运行期间重新定义,以使其工作方式不同,那么调用它的函数将不会是纯函数。
函数组合和部分求值之所以有效,是因为它们提供了自由变量。它是函数式编程中必不可少的抽象方法。例如
function compose(f2, f1) {
return (...args) => f2(f1(...args));
}
function makeAdder(initialValue) {
return v => v + initialValue;
}
const add11 = compose(makeAdder(10), makeAdder(1));
add11(5); // ==> 16
这是纯粹的。闭包变量/自由变量 f1
、f2
、initialValue
永远不会为创建的函数改变。 add11
是一个纯函数。
现在再看compose
。它看起来很纯净,但它可能会被污染。如果传递给它的两个函数都不是纯的,那么结果也不是。
OO 也可以是纯函数式的!
通过不改变您创建的对象,可以轻松地将它们组合起来。
class FunctionalNumber {
constructor(value) {
this.value = value;
}
add(fn) {
return new FunctionalNumber(this.value + fn.value);
}
sub(fn) {
return new FunctionalNumber(this.value - fn.value);
}
}
这个class是纯粹的功能性的。
事实上,您可以将 obj.someMethod(arg1, arg2)
之类的方法调用视为以 obj
作为第一个参数 someFunction(obj, arg1, arg2)
的函数调用。这只是语法上的差异,如果 someFunction
突变 obj
你会说它不是纯粹的。 someMethod
和 obj
也是如此。
您可以创建适用于大型数据结构的 classes,这意味着在执行回溯解算器时,您无需在更改之前复制它。一个简单的例子是 Haskell 和 Lisp 中的对。这是在 JavaScript:
中实现的一种方法
class Cons {
constructor(car, cdr) {
this.car = car;
this.cdr = cdr;
}
}
const lst = new Cons(1, new Cons(2, new Cons(3, null)));
const lst0 = new Cons(0, lst);
lst0
是 lst
但前面有一个新元素。 lst0
重复使用 lst
中的所有内容。从列表到二叉树的一切都可以用它来制作,你可以用不可变的二叉树制作许多顺序数据结构。它从 50 年代就存在了。
我理解你的观点并完全同意@Sylwester 的观点,但有一点值得一提:使用反射可以修改外部常量值并破坏函数的纯粹性。我们知道 IT 中的一切都可能被黑客攻击,我们不应该在概念上考虑这一点,但在实践中我们应该清楚这一点,这样的功能纯粹性是不健全的。
我一直在阅读函数式编程及其概念。我很清楚,在大型项目中工作时,您总是需要混合(在一定程度上)多种范例,例如 OO 和函数式。理论上,函数纯度等概念过于严格如
The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change while program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices. (https://en.wikipedia.org/wiki/Pure_function)
也就是说,这个(或可以被认为是)代码是一个纯函数吗?
const externalVar = 10;
function timesTen(value) {
return externalVar * value;
}
我问这个是因为,在这种情况下,timesTen
函数将 总是 return 相同的输入值,任何人都可以更改 externalVar
的值,因为这是一个常量。但是,这段代码打破了访问外部函数作用域的规则。
是的。保证纯净
原因是它只依赖于绑定和不可变的自由变量。
However, this code breaks the rule of accessing external function's scope.
您的报价中没有任何内容表明您无法访问自由变量。它说外部输入是从文件、网络等读取的,而不是来自先前作用域的自由变量。
甚至 Haskell 也使用像 foldr
这样的全局函数名称,它在每个使用它的函数中都是一个自由变量,当然结果是纯的。
请记住,按名称命名的函数只是变量。 parseInt
是一个指向一个函数的变量,所以如果你应该在另一个函数中使用的每个函数都作为参数传递,那么很难做任何事情。
如果您将 parseInt
重新定义为非纯函数或在程序运行期间重新定义,以使其工作方式不同,那么调用它的函数将不会是纯函数。
函数组合和部分求值之所以有效,是因为它们提供了自由变量。它是函数式编程中必不可少的抽象方法。例如
function compose(f2, f1) {
return (...args) => f2(f1(...args));
}
function makeAdder(initialValue) {
return v => v + initialValue;
}
const add11 = compose(makeAdder(10), makeAdder(1));
add11(5); // ==> 16
这是纯粹的。闭包变量/自由变量 f1
、f2
、initialValue
永远不会为创建的函数改变。 add11
是一个纯函数。
现在再看compose
。它看起来很纯净,但它可能会被污染。如果传递给它的两个函数都不是纯的,那么结果也不是。
OO 也可以是纯函数式的!
通过不改变您创建的对象,可以轻松地将它们组合起来。
class FunctionalNumber {
constructor(value) {
this.value = value;
}
add(fn) {
return new FunctionalNumber(this.value + fn.value);
}
sub(fn) {
return new FunctionalNumber(this.value - fn.value);
}
}
这个class是纯粹的功能性的。
事实上,您可以将 obj.someMethod(arg1, arg2)
之类的方法调用视为以 obj
作为第一个参数 someFunction(obj, arg1, arg2)
的函数调用。这只是语法上的差异,如果 someFunction
突变 obj
你会说它不是纯粹的。 someMethod
和 obj
也是如此。
您可以创建适用于大型数据结构的 classes,这意味着在执行回溯解算器时,您无需在更改之前复制它。一个简单的例子是 Haskell 和 Lisp 中的对。这是在 JavaScript:
中实现的一种方法class Cons {
constructor(car, cdr) {
this.car = car;
this.cdr = cdr;
}
}
const lst = new Cons(1, new Cons(2, new Cons(3, null)));
const lst0 = new Cons(0, lst);
lst0
是 lst
但前面有一个新元素。 lst0
重复使用 lst
中的所有内容。从列表到二叉树的一切都可以用它来制作,你可以用不可变的二叉树制作许多顺序数据结构。它从 50 年代就存在了。
我理解你的观点并完全同意@Sylwester 的观点,但有一点值得一提:使用反射可以修改外部常量值并破坏函数的纯粹性。我们知道 IT 中的一切都可能被黑客攻击,我们不应该在概念上考虑这一点,但在实践中我们应该清楚这一点,这样的功能纯粹性是不健全的。