U组合器
通过将函数作为参数传递给自身,函数可以使用其参数而不是其名称重复出现!因此,给定的函数U
应至少具有一个绑定到该函数(本身)的参数。
在下面的示例中,我们没有退出条件,因此我们将无限期循环直到发生堆栈溢出
const U = f => f (f) // call function f with itself as an argument
U (f => (console.log ('stack overflow imminent!'), U (f)))
我们可以使用多种技术来停止无限递归。在这里,我将编写匿名函数以返回另一个正在等待输入的匿名函数。在这种情况下,一些数字。提供数字后,如果数字大于0,我们将继续重复执行,否则返回0。
const log = x => (console.log (x), x)
const U = f => f (f)
// when our function is applied to itself, we get the inner function back
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
// returns: (x => x > 0 ? U (f) (log (x - 1)) : 0)
// where f is a reference to our outer function
// watch when we apply an argument to this function, eg 5
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0) (5)
// 4 3 2 1 0
这里没有立即显而易见的是,我们的函数在首次使用U
组合器应用于自身时,会返回一个函数,等待第一个输入。如果我们为此命名,则可以使用lambdas(匿名函数)有效地构造递归函数。
const log = x => (console.log (x), x)
const U = f => f (f)
const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
countDown (5)
// 4 3 2 1 0
countDown (3)
// 2 1 0
但这不是直接递归,而是使用自己的名称进行调用的函数。我们对的定义countDown
未在其主体内部引用自己,并且仍然可以递归
// direct recursion references itself by name
const loop = (params) => {
if (condition)
return someValue
else
// loop references itself to recur...
return loop (adjustedParams)
}
// U combinator does not need a named reference
// no reference to `countDown` inside countDown's definition
const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
如何使用U组合器从现有函数中删除自引用
在这里,我将向您展示如何采用使用对自身的引用的递归函数,并将其更改为使用U组合器代替自引用的函数
const factorial = x =>
x === 0 ? 1 : x * factorial (x - 1)
console.log (factorial (5)) // 120
现在使用U组合器替换内部引用 factorial
const U = f => f (f)
const factorial = U (f => x =>
x === 0 ? 1 : x * U (f) (x - 1))
console.log (factorial (5)) // 120
基本的替换模式是这样。请注意,下一节我们将使用类似的策略
// self reference recursion
const foo = x => ... foo (nextX) ...
// remove self reference with U combinator
const foo = U (f => x => ... U (f) (nextX) ...)
Y组合器
相关:使用镜像类比解释U和Y组合器
在上一节中,我们看到了如何使用U组合器将自引用递归转换为不依赖命名函数的递归函数。不得不记住总是将函数作为第一个参数传递给自己,这有点烦人。好吧,Y组合器建立在U组合器的基础上,并删除了那乏味的位。这是一件好事,因为消除/减少复杂性是我们制作函数的主要原因
首先,让我们得出我们自己的Y组合器
// standard definition
const Y = f => f (Y (f))
// prevent immediate infinite recursion in applicative order language (JS)
const Y = f => f (x => Y (f) (x))
// remove reference to self using U combinator
const Y = U (h => f => f (x => U (h) (f) (x)))
现在,我们将看到它的用法与U-combinator的比较。注意,要重现,U (f)
我们可以简单地调用f ()
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
Y (f => (console.log ('stack overflow imminent!'), f ()))
现在,我将使用演示该countDown
程序Y
–您会看到程序几乎相同,但Y组合器使事情更简洁
const log = x => (console.log (x), x)
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const countDown = Y (f => x => x > 0 ? f (log (x - 1)) : 0)
countDown (5)
// 4 3 2 1 0
countDown (3)
// 2 1 0
现在,我们可以看到factorial
,以及
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const factorial = Y (f => x =>
x === 0 ? 1 : x * f (x - 1))
console.log (factorial (5)) // 120
如您所见,它f
成为递归机制。为了重现,我们将其称为普通函数。我们可以使用不同的参数多次调用它,结果仍然正确。并且由于它是一个普通的函数参数,因此我们可以随意命名它,recur
如下所示-
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const fibonacci = Y (recur => n =>
n < 2 ? n : recur (n - 1) + (n - 2))
console.log (fibonacci (10)) // 55
具有多个参数的U和Y组合器
在上面的示例中,我们看到了如何循环并传递参数来跟踪计算的“状态”。但是,如果我们需要跟踪其他状态怎么办?
我们可以使用诸如数组之类的复合数据...
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const fibonacci = Y (f => ([a, b, x]) =>
x === 0 ? a : f ([b, a + b, x - 1]))
// starting with 0 and 1, generate the 7th number in the sequence
console.log (fibonacci ([0, 1, 7]))
// 0 1 1 2 3 5 8 13
但这很糟糕,因为它暴露了内部状态(计数器a
和b
)。如果我们可以打电话fibonacci (7)
得到我们想要的答案,那将是很好。
使用我们对咖喱函数(一元(1-参数)函数的序列)的了解,我们可以轻松实现目标,而无需修改我们Y
对复合数据或高级语言功能的定义或依赖。
fibonacci
仔细查看下面的定义。我们将立即应用0
和1
分别绑定到a
和的对象b
。现在,斐波那契只是在等待提供最后一个参数,该参数将被绑定到x
。递归时,我们必须调用f (a) (b) (x)
(not f (a,b,x)
),因为我们的函数是咖喱形式的。
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const fibonacci = Y (f => a => b => x =>
x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)
console.log (fibonacci (7))
// 0 1 1 2 3 5 8 13
这种模式对于定义各种功能很有用。下面我们将看到使用定义的两个函数Y
组合子(range
和reduce
)和衍生物reduce
,map
。
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const range = Y (f => acc => min => max =>
min > max ? acc : f ([...acc, min]) (min + 1) (max)) ([])
const reduce = Y (f => g => y => ([x,...xs]) =>
x === undefined ? y : f (g) (g (y) (x)) (xs))
const map = f =>
reduce (ys => x => [...ys, f (x)]) ([])
const add = x => y => x + y
const sq = x => x * x
console.log (range (-2) (2))
// [ -2, -1, 0, 1, 2 ]
console.log (reduce (add) (0) ([1,2,3,4]))
// 10
console.log (map (sq) ([1,2,3,4]))
// [ 1, 4, 9, 16 ]
这一切都是陌生的
因为我们在这里使用纯函数,所以可以用任何命名函数代替其定义。观看当我们使用斐波那契并将命名函数替换为其表达式时会发生什么
/* const U = f => f (f)
*
* const Y = U (h => f => f (x => U (h) (f) (x)))
*
* const fibonacci = Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)
*
*/
/*
* given fibonacci (7)
*
* replace fibonacci with its definition
* Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
*
* replace Y with its definition
* U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
//
* replace U with its definition
* (f => f (f)) U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
*/
let result =
(f => f (f)) (h => f => f (x => h (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
console.log (result) // 13
一切就在这里–仅fibonacci (7)
使用匿名函数就可以递归计算