钩、线和沉降片
我不能强调是多么的重要,你不要让所有的新条款陷入僵局,那感觉就像你要学会-函数式编程是关于功能-也许你需要了解有关功能的唯一的事情是,它允许您使用参数抽象程序的一部分;或多个参数,如果需要(不是)并且您的语言支持(通常是)
我为什么要告诉你这些?JavaScript 已经有一个非常好的 API 来使用内置的对异步函数进行排序,Promise.prototype.then
// never reinvent the wheel
const _pipe = (f, g) => async (...args) => await g( await f(...args))
myPromise .then (f) .then (g) .then (h) ...
但是您想编写函数式程序,对吗?这对于函数式程序员来说没有问题。隔离你想要抽象(隐藏)的行为,然后简单地将它包装在一个参数化的函数中——现在你有了一个函数,继续以函数式风格编写你的程序......
在你这样做一段时间之后,你开始注意到抽象模式——这些模式将作为你稍后学习的所有其他事物(函子、应用程序、单子等)的用例——但将它们留到以后使用——因为现在,函数...
下面,我们演示了左到右通过的异步函数组成comp
。就本程序而言,delay
作为 Promises 创建者包含在内,sq
并且add1
是示例异步函数 -
const delay = (ms, x) =>
new Promise (r => setTimeout (r, ms, x))
const sq = async x =>
delay (1000, x * x)
const add1 = async x =>
delay (1000, x + 1)
// just make a function
const comp = (f, g) =>
// abstract away the sickness
x => f (x) .then (g)
// resume functional programming
const main =
comp (sq, add1)
// print promise to console for demo
const demo = p =>
p .then (console.log, console.error)
demo (main (10))
// 2 seconds later...
// 101
创造你自己的便利
你可以创建一个compose
接受任意数量函数的可变参数——还要注意这如何允许你在同一个组合中混合同步和异步函数——直接插入的好处.then
,它会自动将非 Promise 返回值提升为 Promise -
const delay = (ms, x) =>
new Promise (r => setTimeout (r, ms, x))
const sq = async x =>
delay (1000, x * x)
const add1 = async x =>
delay (1000, x + 1)
// make all sorts of functions
const effect = f => x =>
( f (x), x )
// invent your own convenience
const log =
effect (console.log)
const comp = (f, g) =>
x => f (x) .then (g)
const compose = (...fs) =>
fs .reduce (comp, x => Promise .resolve (x))
// your ritual is complete
const main =
compose (log, add1, log, sq, log, add1, log, sq)
// print promise to console for demo
const demo = p =>
p .then (console.log, console.error)
demo (main (10))
// 10
// 1 second later ...
// 11
// 1 second later ...
// 121
// 1 second later ...
// 122
// 1 second later ...
// 14884
更聪明地工作,而不是更努力
comp
并且compose
是易于理解的函数,几乎不费吹灰之力就可以编写。因为我们使用了 built-in .then
,所有错误处理的东西都会自动为我们连接起来。您不必担心手动await
'ing 或try/catch
or .catch
'ing -以这种方式编写我们的函数的另一个好处 -
抽象无耻
现在,这并不是说每次你编写抽象都是为了隐藏一些不好的东西,但它对各种任务非常有用——例如“隐藏”命令式风格while
——
const fibseq = n => // a counter, n
{ let seq = [] // the sequence we will generate
let a = 0 // the first value in the sequence
let b = 1 // the second value in the sequence
while (n > 0) // when the counter is above zero
{ n = n - 1 // decrement the counter
seq = [ ...seq, a ] // update the sequence
a = a + b // update the first value
b = a - b // update the second value
}
return seq // return the final sequence
}
console .time ('while')
console .log (fibseq (500))
console .timeEnd ('while')
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ... ]
// while: 3ms
但是您想编写函数式程序,对吗?这对于函数式程序员来说没有问题。我们可以创建自己的循环机制,但这次它将使用函数和表达式而不是语句和副作用——所有这些都不会牺牲速度、可读性或堆栈安全性。
在这里,loop
使用我们的recur
值容器连续应用一个函数。当函数返回一个非recur
值时,计算完成,并返回最终值。fibseq
是一个具有无限递归的纯函数表达式。这两个程序都在大约 3 毫秒内计算出结果。不要忘记检查答案是否匹配:D
const recur = (...values) =>
({ recur, values })
// break the rules sometimes; reinvent a better wheel
const loop = f =>
{ let acc = f ()
while (acc && acc.recur === recur)
acc = f (...acc.values)
return acc
}
const fibseq = x =>
loop // start a loop with vars
( ( n = x // a counter, n, starting at x
, seq = [] // seq, the sequence we will generate
, a = 0 // first value of the sequence
, b = 1 // second value of the sequence
) =>
n === 0 // once our counter reaches zero
? seq // return the sequence
: recur // otherwise recur with updated vars
( n - 1 // the new counter
, [ ...seq, a ] // the new sequence
, b // the new first value
, a + b // the new second value
)
)
console.time ('loop/recur')
console.log (fibseq (500))
console.timeEnd ('loop/recur')
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ... ]
// loop/recur: 3ms
没有什么是神圣的
请记住,您可以为所欲为。没有什么神奇的then
- 某个地方的某个人决定成功。你可以成为某个地方的某个人,然后自己制作then
——这then
是一种前向组合函数——就像Promise.prototype.then
,它自动应用于then
非then
返回值;我们添加这个并不是因为这是一个特别好的主意,而是为了表明如果我们愿意,我们可以做出这种行为。
const then = x =>
x && x.then === then
? x
: Object .assign
( f => then (f (x))
, { then }
)
const sq = x =>
then (x * x)
const add1 = x =>
x + 1
const effect = f => x =>
( f (x), x )
const log =
effect (console.log)
then (10) (log) (sq) (log) (add1) (add1) (add1) (log)
// 10
// 100
// 101
sq (2) (sq) (sq) (sq) (log)
// 65536
那是什么语言?
它甚至不再像 JavaScript,但谁在乎呢?这是你的程序,你决定你想要它的样子。一门好的语言不会妨碍您并强迫您以任何特定的风格编写程序;功能或其他。
它实际上是 JavaScript,只是不受对其表达能力的误解的束缚 -
const $ = x => k =>
$ (k (x))
const add = x => y =>
x + y
const mult = x => y =>
x * y
$ (1) // 1
(add (2)) // + 2 = 3
(mult (6)) // * 6 = 18
(console.log) // 18
$ (7) // 7
(add (1)) // + 1 = 8
(mult (8)) // * 8 = 64
(mult (2)) // * 2 = 128
(mult (2)) // * 2 = 256
(console.log) // 256
当你理解了$
,你就理解了所有单子之母。记住要专注于机制并对其工作原理有一个直觉;少担心条款。
装运它
我们只是在我们的本地代码片段中使用了名称comp
和名称compose
,但是当您打包程序时,您应该根据您的特定上下文选择有意义的名称——请参阅 Bergi 的评论以获得建议。