繁体   English   中英

Crockford的Javascript Applicative Order Y Combinator语法构造解释?

[英]Crockford's Javascript Applicative Order Y Combinator syntax construct explanation?

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

正在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一样(因此我可以按该术语进行搜索)。

欢迎链接到文档。

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

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

我的困惑是由于不了解:

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

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

Crockford的示例使用变量y。

与其对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什么呢? 以及我们如何填写... 认识U U简单地将一个函数应用于自身-

 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 => ... ,当您调用它时,我将处理将x传递给某些U (recur) (...)递归U (recur) (...)
const Y = f =>
  f (x => ...) // <-- instead of f (f)...

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

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

因此,某些函数f Y将创建递归机制U (recur => ...) ,它将使用函数x => ...调用f ,该函数在应用时将使用U (recur) (x)使用x递归U (recur) (x) -

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 


番茄,番茄

克罗克福德将Y组合器称为“ ...计算机科学中最奇怪,最奇妙的工件之一” ,但是我认为这是因为他不理解它,并且继续复制/粘贴您在互联网上看到的相同的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的原始定义 -

 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 ]

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM