如何使用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)); 

我开始写trace2认为跟踪不起作用,因为“如何可以轻点可能知道关于n或x什么?”。如何使用Lodash流程了解咖喱和功能组成?

但是跟踪确实有效,我不明白它是如何将来自流量的x注入到tap调用中的。任何解释将不胜感激:)

+1

'tap'是curried,这意味着如果你没有传递足够的参数,它会返回一个仍然需要其余参数的函数。 – 4castle

银匙评价

我们只需要开始追查

addSquare(3, 1) // ... 

这样的,所谓

= 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 

这样评价你无法看到的基本“技巧”是trace('after add')返回一个函数,它正在等待最后一个rgument。这是因为trace是一个双参数函数,它是curried


无用

我无法表达多么无用和误解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 
    } 
} 

当然,这“作品”因为它描述的工作,但它允许您创建可怕的脆弱代码。

  • 环通提供的所有功能,键入检查他们
  • 环通提供的所有功能,再次应用它们
  • 出于某种原因,允许第一函数(第一功能)有特殊行为接受1 或更多自变量传入
  • 所有非第一个函数最多只能接受1个参数
  • 在使用空流的情况下,除了您的第一个输入参数之外的所有内容都将被丢弃

如果您问我,那很奇怪f'合同。你应该问:

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

经典函数组合的两个功能,fg

排版 - 允许数据似乎从瞬间移动状态A直接状态C。当然B状态仍然发生在幕后,但我们可以从认知负载中删除这一事实是一项巨大的礼物。

function composition

组成和讨好发挥,从而很好地协同因为

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

Let's rewri忒代码现在

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

“嘿,你骗我!这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

哦,顺便说一句,我们重新实现flow用一个单一的代码行了。


再次

好骗,所以你可能会注意到32论点在不同的地方通过。你会认为你已经被欺骗了。

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

console.log(tripleSquare(2)) //=> 36

不过的事实是这样的:只要你介绍一个单一的非一元函数到您的函数组合,您不妨重构你的代码。可读性立即下降。如果它会损害可读性,试图保持代码无任何意义是绝对没有意义的。

比方说,我们必须保持这两个参数可用于您的原始addSquare函数&hellip;那将是什么样子?

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

OK,所以我们不得不定义addSquare,因为这

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

这当然不是因为聪明作为lodash版本,但它在如何的明确术语被组合在一起并且几乎具有零复杂性。

事实上,7行代码在这里实现少整个程序比它采取单独落实刚刚lodash flow功能。


大惊小怪,为什么

一切都在你的程序是一个权衡。我不希望看到初学者与简单的事情斗争。与做这些事情图书馆工作如此复杂是非常令人沮丧的 - 甚至不让我开始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) 

两种类型的函数组合

还有一件事我应该提到:经典的函数组合应用功能从右到左。因为有些人觉得难读/推理,左到右功能作曲家像flowpipe流行库

rtl vs ltr function composition

已经出现了
  • 左到右的作曲家flow,易于被命名,因为你的眼睛会在意大利面条的形状,您尝试跟踪的数据流因为它移动通过你的节目。(LOL)

  • 从右到左作曲家,composer,会让你觉得你在第一反向读取,但稍加练习之后,开始觉得很自然。它不会受到意大利面形状数据追踪的困扰。

+0

@Tirke,你的问题比你可能意识到的更广泛,我只是用这里提出的主题来描述表面。如果我能以其他任何方式提供帮助,请让我知道^ _^ – naomik

+0

我甚至无法想象这样一个细致而深思熟虑的答案。这正是我所需要的,现在我确定有更深入的了解。 我目前没有关于流程或构图的任何问题,但如果我再次陷入fp的奇妙世界,我会记住你:)感谢1000x。 – Tirke

+0

@Tirke乐意帮忙!我添加了一个关于RTL与LTR函数组合的注释。毫不奇怪,“流动”的整个存在使得你的生活变得更加困难 - 一旦你开始认识“写作”,你会想知道为什么有人试图“修补”那些尚未破碎的东西。 – naomik