U组合器
通过将一个函数作为参数传递给自身,函数可以使用其参数而不是名称来进行递归! 因此,给定给 U
的函数应至少有一个参数,该参数将绑定到该函数(它本身)。
在下面的示例中,我们没有退出条件,因此我们会无限循环,直到发生堆栈溢出。
const U = f => f (f)
U (f => (console.log ('stack overflow imminent!'), U (f)))
我们可以使用各种技术来停止无限递归。在这里,我将编写我们的匿名函数,以返回等待输入的
另一个 匿名函数; 在本例中,是一些数字。当提供一个数时,如果它大于0,我们将继续递归,否则返回0。
const log = x => (console.log (x), x)
const U = f => f (f)
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0) (5)
这里并不立即显而易见的是,当我们首次使用 U
组合子将函数应用于自身时,它会返回一个等待第一个输入的函数。如果我们给它一个名称,就可以有效地使用 lambda 表达式(匿名函数)构建递归函数。
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)
countDown (3)
只是这不是直接递归——一个使用自己的名字调用自己的函数。我们定义的countDown
函数在其主体内没有引用自身,但仍然可以进行递归。
const loop = (params) => {
if (condition)
return someValue
else
return <b>loop</b> (adjustedParams)
}
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))
现在使用U组合子来替换对factorial
的内部引用
const U = f => f (f)
const factorial = U (f => x =>
x === 0 ? 1 : x * U (f) (x - 1))
console.log (factorial (5))
基本的替换模式是这样的。请注意,我们将在下一部分中使用类似的策略。
// 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 (<b>x =></b> Y (f) <b>(x)</b>)
// remove reference to self using U combinator
const Y = <b>U (h =></b> f => f (x => <b>U (h)</b> (f) (x))<b>)</b>
现在我们将看到它的使用与 U-combinator 的比较。请注意,为了进行递归,我们可以简单地调用 f()
而不是 U(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 ()))
现在我将演示使用Y
的countDown
程序 - 您会发现这两个程序几乎相同,但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)
countDown (3)
现在我们将看到 阶乘
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))
正如您所看到的,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))
具有多个参数的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]))
console.log (fibonacci ([0, 1, 7]))
但这样做是不好的,因为它暴露了内部状态(计数器a
和b
)。如果我们只能调用fibonacci (7)
来获得我们想要的答案就好了。
利用我们对柯里化函数(一次只接受一个参数的函数序列)的了解,我们可以轻松实现我们的目标,而无需修改Y
的定义,也不需要依赖于复合数据或高级语言特性。
仔细看一下下面的fibonacci
的定义。我们立即应用了绑定到a
和b
上的0
和1
。现在,fibonacci
只是在等待最后一个参数被提供,并将其绑定到x
上。当我们进行递归时,我们必须调用f(a)(b)(x)
(而不是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))
这种模式可用于定义各种函数。下面我们将看到使用
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))
console.log (reduce (add) (0) ([1,2,3,4]))
console.log (map (sq) ([1,2,3,4]))
一切都是匿名的,哇哦
因为我们在这里使用的是纯函数,所以我们可以用其定义替换任何命名函数。看看当我们将斐波那契数列中的命名函数替换为它们的表达式时会发生什么。
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)
就是这样 - 使用匿名函数递归计算fibonacci(7)
arguments.callee
存在,而这个函数并没有做任何有用的事情。我正在查找Y组合子,该东西永远不会变得有用... - Kobi