钓鱼竿,线和沉重的铅坠
我强调你不要被所有新术语所困扰——函数式编程是关于“函数”的。也许你只需要了解函数允许你使用参数来抽象你程序的一部分;如果需要(一般不需要),还可以使用多个参数,这取决于你的编程语言是否支持。
我为什么要告诉你这些?因为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
作为Promise创建器被包含在其中,而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)
const comp = (f, g) =>
x => f (x) .then (g)
const main =
comp (sq, add1)
const demo = p =>
p .then (console.log, console.error)
demo (main (10))
创造自己的便利
您可以创建一个可变参数的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)
const effect = f => x =>
( f (x), x )
const log =
effect (console.log)
const comp = (f, g) =>
x => f (x) .then (g)
const compose = (...fs) =>
fs .reduce (comp, x => Promise .resolve (x))
const main =
compose (log, add1, log, sq, log, add1, log, sq)
const demo = p =>
p .then (console.log, console.error)
demo (main (10))
更聪明地工作,而不是更辛苦
comp
和 compose
是易于理解的函数,几乎不需要任何努力就可以编写。因为我们使用了内置的.then
,所有错误处理都会自动钩连起来。您无需担心手动await
或try/catch
或.catch
- 这是我们以这种方式编写函数的又一个好处。
抽象没有什么可耻的
这并不意味着每次编写抽象时都是为了隐藏一些不好的东西,但它可以非常有用地完成各种任务 - 例如“隐藏”命令式风格的while
循环。
const fibseq = n =>
{ let seq = []
let a = 0
let b = 1
while (n > 0)
{ n = n - 1
seq = [ ...seq, a ]
a = a + b
b = a - b
}
return seq
}
console .time ('while')
console .log (fibseq (500))
console .timeEnd ('while')
但是你想编写函数式程序,对吧?对于函数式程序员来说这不是问题。我们可以制作自己的循环机制,但这次它将使用函数和表达式而不是语句和副作用-所有这些都不会牺牲速度、可读性或堆栈安全性。
在这里,loop
不断地使用我们的 recur
值容器应用函数。当函数返回一个非 recur
值时,计算完成,最终值被返回。fibseq
是一个纯粹的、具有无限递归的函数式表达式。这两个程序都可以在大约3毫秒内计算出结果。别忘了检查答案是否匹配:D
const recur = (...values) =>
({ recur, values })
const loop = f =>
{ let acc = f ()
while (acc && acc.recur === recur)
acc = f (...acc.values)
return acc
}
const fibseq = x =>
loop
( ( n = x
, seq = []
, a = 0
, b = 1
) =>
n === 0
? seq
: recur
( n - 1
, [ ...seq, a ]
, b
, a + b
)
)
console.time ('loop/recur')
console.log (fibseq (500))
console.timeEnd ('loop/recur')
没有什么是神圣的
记住,你可以做任何想做的事情。 then
没有什么神奇的地方 - 在某个地方,某人决定将其制作。 你可以成为某个地方的某个人,并且只需制作自己的 then
- 这里的 then
是一种前向组合函数 - 就像 Promise.prototype.then
一样,它会自动将 then
应用于非 then
返回值; 我们添加这个不是因为这是一个特别好的想法,而是为了表明如果我们想要的话,我们可以制作出那种行为。
const then = 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)
sq (2) (sq) (sq) (sq) (log)
这是什么语言?
看起来甚至不像JavaScript了,但是谁在意呢?这是你的程序,你可以决定它的样子。好的语言不会阻止你按照任何特定风格编写程序,无论是函数式还是其他风格。
实际上,它就是JavaScript,只是没有限制它所能表达的误解 -
const $ = x => k =>
$ (k (x))
const add = x => y =>
x + y
const mult = x => y =>
x * y
$ (1)
(add (2))
(mult (6))
(console.log)
$ (7)
(add (1))
(mult (8))
(mult (2))
(mult (2))
(console.log)
当你理解了$
,你就理解了所有monad之母。记得关注它的机制并对它是如何工作的有直觉;不要过于担心术语。
发布它
我们刚刚在本地片段中使用了comp
和compose
这两个名称,但是当您打包程序时,应该选择符合您特定上下文的名称-请参见Bergi的评论以获取建议。
arguments.
是一个错别字,还是我从未见过的语法? - JLRishe