【问题标题】:Crockford's Javascript Applicative Order Y Combinator syntax construct explanation?Crockford 的 Javascript Applicative Order Y Combinator 语法构造解释?
【发布时间】:2019-09-14 13:38:59
【问题描述】:

想了解语法结构的作用/查找涵盖该主题的适当文档。

https://www.crockford.com/little.html阅读 Crockford 的 The Little JavaScripter

我冒昧地重新格式化代码以指出我感到困惑的地方。

注意:错误地格式化这段代码太容易了,解析器插入可能会妨碍执行并导致阶乘无效函数(也许值得全新的问题)

function Y(le) {
    // just one return
    return (
        // ok, so far it returns a function
        function (f) { return f(f); }
        // here it starts to be syntaxically puzzling
        // what does following part, after already returning 
        // the function do?
        // construct looks like 'return ( f1(x) (f2 (x)) )'... 
        // looks very lispy
        (function (f) { 
            return le(function (x) { 
                return f(f)(x); 
            } ); 
        } )
    );
}

var factorial = Y(function (fac) {
    return function (n) {
        return (
            n <= 2
            ? n
            : n * fac(n - 1)
        );
    };
});

var number120 = factorial(5);
document.write(number120);

我知道所谓的 IIFE 语法结构 https://developer.mozilla.org/en-US/docs/Glossary/IIFE 但是这里使用的构造并不是真正遵循 IIFE(据我了解 IIFE)。这是一种颠倒的构造,看起来像是最近引入 JavaScript 的其他类型的函数构造,但我找不到涵盖这种用法的文档部分。有没有像 IIFE 一样涵盖这个结构的术语(所以我可以按术语搜索)。

欢迎提供文档链接。

【问题讨论】:

  • return ( 中的代码是一个 IIFE ...
  • 现在我明白了。是'return(function(f)/definition/......()/立即执行/)' 非常感谢!
  • 只是为了好玩......这是现代javascript中的代码const Y = le =&gt; (f =&gt; f(f))(f =&gt; le((x) =&gt; f(f)(x)));const factorial = Y(fac =&gt; n =&gt; n &lt;= 2 ? n : n * fac(n - 1));:p
  • crockford 中的语法仍然与 MDN IIFE 示例有点不同。但我猜这里使用的是特定的 IIFE 变体。因此我的困惑。特别是 (function () { statements } ( farg )) 而不是 (function () { statements })();。这就是为什么我不认为它是 IIFE 构造的原因。除非当然是别的东西。

标签: javascript


【解决方案1】:

一个IIFE。如果我们将其格式化为:

  return (function (f) { return f(f); })(
    function (f) { 
        return le(function (x) { 
            return f(f)(x); 
        }
    }
 });

【讨论】:

  • MDN 上的 IIFE 示例没有明确提及执行部分在其中包含表达式。但显然它可以拥有它(只是没有将其视为用例)。很难对其进行格式化以使其工作并采用易于遵循的相同提示结构。也许当 IIFE 变得更普遍时,它会更容易被发现。
  • 请修正示例的语法。
【解决方案2】:

与其对 Crockford 的 Y 进行逆向工程,不如先从其目的开始理解 Y。当我们从一个意图开始并按照我们的方式向前 找到解决方案时,我们就可以开始了解Y 的工作原理。

“我想写一个递归公式……不使用递归”

早在人们广泛使用计算机之前,数学家和逻辑学家就面临着这个问题。这就是Y 组合器解决的问题,但它是如何做到的呢?让我们先从我们要写的递归“公式”开始——

const fact = n =>
  n <= 2
    ? n
    : n * fact (n - 1) // <-- recursive!

fact 以上是根据自身定义的。我们可以通过使用一个额外的参数来控制递归来删除这个自引用,recur -

const fact =          n =>
const fact = recur => n =>
  n <= 2
    ? n
    : n * fact (n - 1)
    : n * ...  (n - 1)

但是recur 应该是什么?以及我们如何填写...?认识UU 只是将一个函数应用于自身 -

const U = f =>
  f (f)                     // <-- apply f to itself

const fact = recur => n =>
  n <= 2
    ? n
    : n * U (recur) (n - 1) // <-- apply recur to itself

console.log (U (fact) (5))  // <-- apply fact to itself
// 120

上面我们调用U (fact) (5) 来计算结果。重要的是要注意,当我们定义 fact = ... 时,我们可以应用 U -

const U = f =>
  f (f)

const fact = U (recur => n => // <-- apply U directly on lambda
  n <= 2
    ? n
    : n * U (recur) (n - 1))

console.log (fact (5)) // <-- call fact normally
// 120

使用U,我们现在有了一种编写递归表达式的通用方法-

U (recur => ...      U (recur) ...)

“我不喜欢这样,我必须记住使用U (recur) (...)。我可以直接打电话给recur (...)吗?”

U 是一个非常简单的机制,但确实有点麻烦,因为我们必须记住每次重复时都将recur“反射”回自身。这就是Y 组合子与U 的不同之处。 Y 说,

  • 给我函数f,它要求递归机制
  • 我会给你一个递归机制,x =&gt; ...,当你调用它时,我会处理将x传递给一些U (recur) (...)
const Y = f =>
  f (x => ...) // <-- instead of f (f)...

就像我们在上面看到的 fact 一样,我们可以使用 U 编写递归表达式 -

U (recur => ...      U (recur) ...)

因此,某个函数fY 将创建一个递归机制U (recur =&gt; ...),它会使用函数f 调用x =&gt; ...,当应用该函数时,将使用@ 与x 一起递归987654363@-

const Y = f =>
  //            f (x =>    ...    (x))
  //            =======           ====
  //               |               |
  //               v               v
  // U (recur => ...    U (recur) ...)

     U (recur => f (x => U (recur) (x)))

让我们看看下面的Y -

const U = f =>
  f (f)

const Y = f =>
  U (recur => f (x => U (recur) (x)))

const fact = Y (recur => n =>
  n <= 2
    ? n
    : n * recur (n - 1)) // <-- call "recur" normally

console.log (fact (5)) // <-- call "fact" normally
// 120

番茄,番茄

Crockford 将 Y-combinator 称为 “......计算机科学中最奇怪和最奇妙的神器之一”,但我认为那是因为他不理解它并继续复制/粘贴您在互联网上看到的相同Y 示例。没有什么奇怪,除非你以一种混淆所有含义的方式编写它 -

// replace IIFE with U
function Y(le) {
  return (
    function (f) { return f(f); }
    U
    (function (f) { 
      return le(function (x) { 
        return f(f)(x);
        return U(f)(x) 
      }); 
    })
  )
}

// remove unnecessary outer (...)
function Y(le) {
  return U(function (f) { 
    return le(function (x) { 
      return U(f)(x)
    })
  })
}

// simplified arrow expression
const Y = le =>
  U (f => le (x => U (f) (x))

// alpha rename "f" to "recur"
const Y = le =>
  U (recur => le (x => U (recur) (x))

// alpha rename "le" to "f"
const Y = f =>
  U (recur => f (x => U (recur) (x))

与我们上面的Y 版本相匹配 -

const Y = f =>
  U (recur => f (x => U (recur) (x)))

没有你

我们可以扩展U 使得Y 有一个独立的定义-

// starting with 
const Y = f =>
  U (recur => f (x => U (recur) (x)))

// expand inner U
const Y = f =>
  U (recur => f (x => recur (recur) (x)))

// expand outer U
const Y = f =>
  (recur => f (x => recur (recur) (x)))
  (recur => f (x => recur (recur) (x)))

我们又回到了 IIFE,但这次我们的定义更接近 Haskell Curry 的 original definition -

const Y = f =>
  (recur => f (x => recur (recur) (x)))
  (recur => f (x => recur (recur) (x)))

const fact = Y (recur => n =>
  n <= 2
    ? n
    : n * recur (n - 1))

console.log (fact (5))
// 120

扩展我们的 Y 直觉

与几乎所有其他 Y 教程一样,Crockford 演示了在具有单个参数的函数上使用 Y。除了对Y 的初步理解之外,还有很多潜在的潜伏。我将向您展示带有 2 个甚至 3 个参数的简单函数 -

const Y = f =>
  (recur => f (x => recur (recur) (x)))
  (recur => f (x => recur (recur) (x)))

const range1 = Y (recur => m => n =>
  m > n
    ? []
    : [ m, ...recur (m + 1) (n) ])

const range2 = Y (recur => r => m => n => 
  m > n
    ? r
    : recur ([ ...r, m ]) (m + 1) (n)) ([])

const fib = Y (recur => a => b => n =>
  n === 0
    ? a
    : recur (b) (a + b) (n - 1)) (0) (1)

console.log (range1 (5) (10))
// [ 5, 6, 7, 8, 9, 10 ]

console.log (range2 (5) (10))
// [ 5, 6, 7, 8, 9, 10 ]

console.log (fib (10))
// 55

Y可以表达的程序复杂度真的没有限制-

const Y = f =>
  (recur => f (x => recur (recur) (x)))
  (recur => f (x => recur (recur) (x)))

const reduce = Y (recur => i => f => state => a =>
  i >= a.length
    ? state
    : recur (i + 1) (f) (f (state, a[i])) (a)) (0)

const filter = f =>
  reduce ((r, x) => f (x) ? [ ...r, x ] : r) ([])

const odds =
  filter (n => n & 1)

console.log (odds ([ 0, 1, 2, 3, 4, 5, 6 ]))
// [ 1, 3, 5 ]

【讨论】:

  • 相关:参见this Q&A 使用镜像类比对Y 的解释。
  • 多好!尽管我之前对这段代码试图解决的一般问题有所了解,但很高兴对这个概念进行如此详尽的解释。我唯一的评论(不在 Y-combinator 上)是,如果语言允许将函数作为参数传递,那么它很可能本身就已经将递归作为机制。另一方面,如果语言在其设计中没有递归,那么它也有函数参数(或作者所说的第一类函数)的可能性很小。然而,这个答案在计算的学习方面有更多的价值。
【解决方案3】:

我的困惑是由于不了解:

var x = (function (a) {return a})(1);
var y = (function (a) {return a} (2));
var z = function (a) {return a} (3);

都是同一种类型的 IIFE 变体。至少所有 3 个都以相同的方式被识别和执行(在现代浏览器中)。 MDN 需要澄清这一点。

MDN 解释 IIFE 的形式是 x

Crockford 的示例使用变体 y。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-04-28
    • 1970-01-01
    • 1970-01-01
    • 2016-06-07
    • 2015-01-31
    • 2017-08-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多