JavaScript 中如何组合高阶函数?

问题描述 投票:0回答:1

不幸的是,我买的第一本关于 JavaScript 的书并不适合初学者。 这本书是 Luis Atencio 的《JavaScript 的乐趣》。两年后我仍在尝试理解这本书中的一些概念......

有一个代码我今天还是不明白。你能让我明白这怎么可能吗?

const identity = a => a;
const fortyTwo = () => 42;

const notNull = a => a !== null;
const square  = a => a ** 2;

const safeOperation = (operation, guard, recover) =>
  input => guard(input, operation) || recover();

const onlyIf = validator => (input, operation) =>
  validator(input) ? operation(input) : NaN;

const orElse = identity;

const safeSquare = safeOperation(
  square,
  onlyIf(notNull),
  orElse(fortyTwo)
);

console.log( safeSquare(2) ); // 4
console.log( safeSquare(null) ); // 42

safeSquare(2)
是一个具有 3 个参数的函数(
safeSquare
square
onlyIf(null)
)时,这怎么可能做到
orElse(fortyTwo)
。我认为这里的2是
safeOperation
中的输入,但是2在
safeOperation
函数中是如何传递的。我只是不明白这个。

我知道柯里化的基础知识,但在这里我不明白。

javascript functional-programming callback higher-order-functions currying
1个回答
0
投票

一些理论

首先,我们来谈谈一些理论。即,什么是β减少。维基百科有很多理论,但简单的解释是你可以用它的应用程序替换函数。例如,如果您有

const fn = x => x + 1;

const result = fn(2);

最后一部分可以替换为函数体:

const fn = x => x + 1;
//              ^^^^^^ -----+ for f(2)
//                          | then x = 2
const result = 2 + 1; // <--+ therefore we can substitute here

这两段代码是等价的。 β 缩减的名称并不重要,重要的是这里的原理。函数的调用可以用函数体替换,但参数被替换。

即使函数返回另一个函数,这也成立:

const add = a => b => a + b;
这是一个带有一个参数的函数,返回第二个带有另一个参数的函数。当调用第二个函数时,那么最终的结果就是第一个参数

a

加上第二个参数
b

为了清楚起见,这与更长的书写方式相同:

function add(a) { return function(b) { return a + b; } }
但是,如果我们应用 beta 缩减,则应用与以前相同的规则,调用 

add(2)

 可以替换为正文,其中 
a = 2
:

const add = a => b => a + b; // ^^^^^^^^^^^ -----+ for add(2) // | a = 2 const result1 = b => 2 + b; // <--+ therefore we can substitute const result2 = add(2); // same as the above console.log( result1(3) ); // 2 + 3 = 5 console.log( result2(3) ); // 2 + 3 = 5

现在我们可以看看如何使用它。

现在练习一下

β 减少原理现在可以告诉我们这段代码是如何工作的:

safeSquare(2)

 是一个具有 3 个参数的函数时,这怎么可能 
safeSquare
square
onlyIf(null)
orElse(fortyTwo)

safeOperation

函数是带有三个参数的函数。因此,它的调用可以用body来代替

const safeOperation = (operation, guard, recover) => input => guard(input, operation) || recover(); //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the body that we can substitute
制作

const safeSquare = safeOperation( square, // ------------------------------------------------+ onlyIf(notNull), // ---------------------------------------------+ | orElse(fortyTwo) // ------------------------------------------+ | | ); // | | | // equivalent to: | | | input => onlyIf(notNull)(input, square) || orElse(fortyTwo)()// | | | // ^^^^^^^^^^^^^^^ ^^^^^^ ^^^^^^^^^^^^^^^^ | | | // ^ ^ ^ | | | // | | | | | | // | | +------------+ | | // | +--------------------------------+ | // +-----------------------------------------------------+
我们可以继续测试减少其他功能。然而,这留给读者练习。这里的要点是,当使用三个参数调用 

safeOperation

 时,它返回一个只有一个参数的函数。该参数函数被分配给 
safeSquare
,这就是为什么 
safeSquare(2)
 是一个合法的调用。

回到理论和术语

一流的功能

实现这种行为的东西称为

一流函数(另请参阅维基百科。简单地说,“一流”意味着这是编程语言可以直接处理的一种值类型 - 它可以分配给变量,它可以传递到函数调用等中。例如,其他第一类类型是字符串或数字。我们可以用它们做同样的事情:

const str = "hello"; const num = 4; someFunction( "a string" ); otherFunction( 42 );
当一等函数成为可能时,我们还会得到以下结果:

高阶函数

这些函数至少执行以下操作之一:将函数作为参数或返回函数作为结果。这些是编写可重用代码的基础,其中操作的一部分可以更改。使用高阶函数的好例子是

Array#filter()

Array#map()
(以及其他数组方法)。这两个遍历数组并对每个成员执行 
something.filter()
 将包含或排除它,而 
.map()
 将更改它。对于所有对 
.filter()
.map()
 的调用,检查数组的逻辑是相同的,但检查项目的方式由回调确定:

const fruits = [ 1, 2, 3, 4, 5 ]; const onlyOdd = fruits.filter( x => x % 2 === 1 ); console.log(onlyOdd); // [ 1, 3, 5 ] const allPlus20 = fruits.map( x => x + 20 ); console.log(allPlus20); // [ 21, 22, 23, 24, 25 ]
.as-console-wrapper { max-height: 100% !important; }

函数式编程依赖于高阶函数来构建应用程序并处理数据。

柯里化

这是一种特殊的高阶函数,而不是任何函数。具体来说,采用多个参数的函数将转换为每个需要一个参数的等效系列函数:

const regularAdd = (a, b) => a + b; const curriedAdd = a => b => a + b; const regularPythagoreanCheck = (a, b, c) => a**2 + b**2 === c**2; const curriedPythagoreanCheck = a => b => c => a**2 + b**2 === c**2; //distance between two 2D points const regularDistance = (x1, y1, x2, y2) => Math.sqrt( (x2 - x1)**2 + (y2 - y1)**2 ); const curriedDistance = x1 => y1 => x2 => y2 => Math.sqrt( (x2 - x1)**2 + (y2 - y1)**2 );
等等。当主要在函数中工作时,这种类型的转换非常有用。但我不会太深入——值得记住的是,没有任何高阶函数被柯里化。只要它是一系列单参数函数即可。

现代柯里化的实现没有这那么严格。一种现代的柯里化方法将在每一步接受一个或多个参数,直到满足所有预期参数。例如,使用 Ramda

中的

curry
将函数转换为柯里化变体:

const regularDistance = (x1, y1, x2, y2) => Math.sqrt( (x2 - x1)**2 + (y2 - y1)**2 ); const curriedDistance = R.curry(regularDistance); console.log(regularDistance(1, 1, 4, 5)); // original console.log(curriedDistance(1)(1)(4)(5)); // four 1 parameter functions console.log(curriedDistance(1, 1)(4, 5)); // two 2 parameter functions console.log(curriedDistance(1)(1)(4, 5)); // two 1 parameter functions + one 2 parameter console.log(curriedDistance(1, 1, 4, 5)); // 4 parameter function //etc
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.29.1/ramda.min.js"></script>

© www.soinside.com 2019 - 2024. All rights reserved.