如何使用Lodash流程了解咖喱和功能组成?

Tir*_*rke 9 functional-programming lodash

import {flow, curry} from 'lodash';

const add = (a, b) => a + b;

const square = n => n * n;

const tap = curry((interceptor, n) => {
    interceptor(n);
    return n;
});

const trace2 = curry((message, n) => {
    return tap((n) => console.log(`${message} is  ${n}`), n);
});

const trace = label => {
    return tap(x => console.log(`== ${ label }:  ${ x }`));
};


const addSquare = flow([add, trace('after add'), square]);
console.log(addSquare(3, 1));
Run Code Online (Sandbox Code Playgroud)

我开始写跟踪2认为跟踪不起作用,因为"如何挖掘可能知道n或x什么?".

但跟踪确实有效,我不明白它如何能够"注入"来自流量的x进入点击呼叫.任何解释将不胜感激:)

Tha*_*you 17

银勺评估

我们将从跟踪评估开始

addSquare(3, 1) // ...
Run Code Online (Sandbox Code Playgroud)

好的,这里

= flow([add, trace('after add'), square]) (3, 1)
        add(3,1)
        4
             trace('after add') (4)
             tap(x => console.log(`== ${ 'after add' }:  ${ x }`)) (4)
             curry((interceptor, n) => { interceptor(n); return n; }) (x => console.log(`== ${ 'after add' }:  ${ x }`)) (4)
             (x => console.log(`== ${ 'after add' }:  ${ x }`)) (4); return 4;
             console.log(`== ${ 'after add' }:  ${ 4 }`); return 4;
~log effect~ "== after add: 4"; return 4
             4
                                 square(4)
                                 4 * 4
                                 16
= 16
Run Code Online (Sandbox Code Playgroud)

所以你看到的基本"技巧"是trace('after add')返回一个等待最后一个参数的函数.这是因为咖喱trace是一个双参数函数.


无用

我无法表达这个flow功能是多么无用和误解

function flow(funcs) {
  const length = funcs ? funcs.length : 0
  let index = length
  while (index--) {
    if (typeof funcs[index] != 'function') {
      throw new TypeError('Expected a function')
    }
  }
  return function(...args) {
    let index = 0
    let result = length ? funcs[index].apply(this, args) : args[0]
    while (++index < length) {
      result = funcs[index].call(this, result)
    }
    return result
  }
}
Run Code Online (Sandbox Code Playgroud)

当然,它"工作",因为它被描述为工作,但它允许您创建可怕,脆弱的代码.

  • 循环通过所有提供的函数来键入检查它们
  • 循环通过所有提供的函数再次应用它们
  • 由于某种原因,允许第一个函数(并且只有第一个函数)具有接受传入的一个或多个参数的特殊行为
  • 所有非第一个函数最多只接受1个参数
  • 在使用空流的情况下,除了第一个输入参数之外的所有参数都将被丢弃

如果你问我,那很奇怪的合同.你应该问:

  • 我们为什么要循环两次?
  • 为什么第一个函数会得到特殊的异常?
  • 我以牺牲这种复杂性为代价获得了什么?

经典功能组合

的两种功能的组合物,fg-允许数据看似瞬间移动从状态A直接状态C.当然,状态B仍然发生在幕后,但事实上我们可以从认知负荷中消除这一点是一个巨大的礼物.

功能组成

作曲和曲play一起玩得很好,因为

  1. 函数组合最适用于一元(单参数)函数
  2. curried函数每个应用程序接受1个参数

我们现在重写你的代码吧

const add = a => b => a + b

const square = n => n * n;

const comp = f => g => x => f(g(x))

const comp2 = comp (comp) (comp)

const addSquare = comp2 (square) (add)

console.log(addSquare(3)(1)) // 16
Run Code Online (Sandbox Code Playgroud)

"嘿,你欺骗了我!comp2根本不容易理解!" - 对不起 但这是因为该功能从一开始就注定失败了.为什么?

因为组合最适合一元功能!我们尝试add用一元函数组成二元函数square.

为了更好地说明经典的组成以及如何简单,它可以让我们使用来看一个序列只是一元函数.

const mult = x => y => x * y

const square = n => n * n;

const tap = f => x => (f(x), x)

const trace = str => tap (x => console.log(`== ${str}: ${x}`))

const flow = ([f,...fs]) => x =>
  f === undefined ? x : flow (fs) (f(x))

const tripleSquare = flow([mult(3), trace('triple'), square])

console.log(tripleSquare(2))
// == "triple: 6"
// => 36
Run Code Online (Sandbox Code Playgroud)

哦,顺便说一下,我们也flow用一行代码重新实现了.


再次被吓坏了

好的,所以你可能注意到32参数是在不同的地方传递的.你会认为你被骗了.

const tripleSquare = flow([mult(3), trace('triple'), square])

console.log(tripleSquare(2)) //=> 36
Run Code Online (Sandbox Code Playgroud)

但事实是:只要在函数组合中引入单个非一元函数,您就可以重构代码.可读性立即急剧下降.如果它会损害可读性,那么试图保持代码无点是毫无意义的.

假设我们必须保留原始addSquare函数的两个参数......那会是什么样子?

const add = x => y => x + y

const square = n => n * n;

const tap = f => x => (f(x), x)

const trace = str => tap (x => console.log(`== ${str}: ${x}`))

const flow = ([f,...fs]) => x =>
  f === undefined ? x : flow (fs) (f(x))

const addSquare = (x,y) => flow([add(x), trace('add'), square]) (y)

console.log(addSquare(3,1))
// == "add: 4"
// => 16
Run Code Online (Sandbox Code Playgroud)

好的,所以我们必须定义addSquare为这个

const addSquare = (x,y) => flow([add(x), trace('add'), square]) (y)
Run Code Online (Sandbox Code Playgroud)

它肯定不像lodash版本那么聪明,但它明确了如何组合这些术语并且几乎没有复杂性.

实际上,这里的7行代码实现了整个程序,而不是flow单独实现lodash 函数.


大惊小怪,为什么

你的计划中的一切都是权衡.我不愿意看到初学者在应该简单的事情上挣扎.与使这些事情变得如此复杂的图书馆合作非常令人沮丧 - 甚至没有让我开始研究Lodash的curry实施(包括它非常复杂createWrap)

我的2美分:如果你刚刚开始使用这些东西,那么图书馆就是一把大锤.他们有自己做出的每一个选择的理由,但要知道每个人都需要权衡利弊.所有这些复杂性并非完全没有根据,但作为初学者,这并不是你需要关心的事情.切断基本功能,从那里开始工作.


咖喱

自从我提到以来curry,这里有3行代码几乎取代了Lodash咖喱的任何实际用途.

如果您之后将这些交易用于更复杂的咖喱实施,请确保您知道您从交易中获得了什么 - 否则您只需承担更多的开销而几乎没有收益.

// for binary (2-arity) functions
const curry2 = f => x => y => f(x,y)

// for ternary (3-arity) functions
const curry3 = f => x => y => z => f(x,y,z)

// for arbitrary arity
const partial = (f, ...xs) => (...ys) => f(...xs, ...ys)
Run Code Online (Sandbox Code Playgroud)

两种功能组成

还有一件事我应该提到:经典功能组合应用从右到左的功能.因为有些人发现难以阅读/推理,从左到右的函数作曲家喜欢flow并且pipe已经出现在流行的文章中

rtl vs ltr功能组成

  • 从左到右的作曲家,flow恰如其名,因为当你通过程序移动数据时,你的眼睛会以意大利面形状流动.(大声笑)

  • 从右到左的作曲家,composer会让你感觉自己起初正在向后看,但经过一些练习,它开始感觉非常自然.它不受意大利面形状数据追踪的影响.