简体   繁体   English

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

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

Wanted to understand what syntax construct do / Find appropriate documentation covering the subject. 想了解语法构造的作用/查找涵盖该主题的适当文档。

Was reading Crockford's The Little JavaScripter at https://www.crockford.com/little.html 正在https://www.crockford.com/little.html上阅读Crockford的The Little JavaScripter。

I took a liberty to reformat code a little to point out the place where I get confused. 我随意地重新格式化了代码,以指出我感到困惑的地方。

Note: It is too easy to wrongly format this piece of code, parser insertions can get in the way of execution and render factorial not valid function (perhaps that deserves whole new question) 注意:错误地格式化这段代码太容易了,解析器插入会妨碍执行并呈现阶乘无效功能(也许值得提出新的问题)

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);

I am aware of so called IIFE syntax construct https://developer.mozilla.org/en-US/docs/Glossary/IIFE but construct used here is not really following IIFE (as far as I understand IIFE). 我知道所谓的IIFE语法构造https://developer.mozilla.org/en-US/docs/Glossary/IIFE,但此处使用的构造并没有真正遵循IIFE(据我了解IIFE)。 This is kind of upside down construct and looks like it is some other kind of function construct recently introduced to JavaScript, but I cannot find the part of the documentation that covers this use. 这是一种颠倒的构造,看起来像是最近引入JavaScript的其他某种函数构造,但是我找不到涵盖此用途的文档部分。 Is there a term covering this construct like there is IIFE (so I can search by the term). 是否有一个术语涵盖该构造,就像有IIFE一样(因此我可以按该术语进行搜索)。

Links to documentation are welcome. 欢迎链接到文档。

This is an IIFE. 一个IIFE。 It might he more clear if we format it as: 如果我们将其格式设置为:

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

My confusion was created by not understanding that: 我的困惑是由于不了解:

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

are all IIFE variants of the same kind. 是同一类型的所有IIFE变体。 At least all 3 are recognized and executed in the same fashion (in the modern browser). 至少以相同的方式(在现代浏览器中)识别并执行所有3个。 MDN needs to clarify this. MDN需要澄清这一点。

Crockford's example uses variant y. Crockford的示例使用变量y。

Instead of reverse engineering Crockford's Y , perhaps it's better to understand Y beginning with its purpose first. 与其对Crockford的Y进行逆向工程,不如先从其目的开始理解Y更好。 It's when we start with an intention and work our way forward to a solution that we can begin to understand how Y works. 当我们从一个意图开始并朝着解决方案前进的道路时,我们就可以开始理解Y工作原理。

"I want to write a recursive formula... without using recursion" “我想写一个递归公式...而不使用递归”

This was a problem facing mathematicians and logicians long before people had widespread access to computers. 在人们广泛使用计算机之前,这是数学家和逻辑学家面临的问题。 And this is the problem the Y combinator solves, but how does it do it? 这就是Y组合器要解决的问题,但是它是如何做到的呢? Let's first start with a recursive "formula" we want to write - 让我们首先从我们要编写的递归“公式”开始-

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

Above fact is defined in terms of itself. 以上fact是根据自身定义的。 We can remove this self-reference by using an extra parameter to control recursion, recur - 我们可以通过使用额外的参数来控制递归来删除此自引用, recur

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

But what should recur be? 但是应该recur什么呢? And how do we fill in ... ? 以及我们如何填写... Meet U . 认识U U simply applies a function to itself - 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 

Above we call U (fact) (5) to compute the result. 在上面,我们称U (fact) (5)来计算结果。 It's important to observe that we could've applied U when we defined fact = ... - 重要的是要注意,当我们定义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 

Using U , we now have a generic way to write recursive expressions - 使用U ,我们现在有一种通用的方式来编写递归表达式-

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

"I don't like that I have to remember to use U (recur) (...) . Can I just call recur (...) instead?" “我不喜欢这样,我必须记住使用U (recur) (...) 。我可以只调用recur (...)吗?”

U is an extremely simple mechanism, but indeed it is a little cumbersome because we have to remember to "reflect" recur back onto itself each time we recur. U是一个非常简单的机制,但确实有点麻烦,因为我们必须记住每次recur将“重新反射”回自身。 And this is where the Y combinator differs from the U . 这就是Y组合器与U不同之处。 Y says, Y说,

  • Give me function f , that's asking for a recursion mechanism 给我函数f ,要求递归机制
  • I'll give you a recursion mechanism, x => ... , and when you call it, I will handle passing x to some U (recur) (...) 我将为您提供一个递归机制x => ... ,当您调用它时,我将处理将x传递给某些U (recur) (...)递归U (recur) (...)
const Y = f =>
  f (x => ...) // <-- instead of f (f)...

Just like we saw with fact above, we can write a recursive expression using U - 就像上面看到的fact ,我们可以使用U编写递归表达式

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

So Y of some function f , will create a recursion mechanism, U (recur => ...) and it will call f with a function, x => ... , that when applied, will recur with x using U (recur) (x) - 因此,某些函数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)))

Let's see Y in action below - 让我们在下面看到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 


tomato, tomahto 番茄,番茄

Crockford calls the Y -combinator "...one of the most strange and wonderful artifacts of Computer Science" , but I think that's because he doesn't understand it and continues to copy/paste the same Y examples you see around the internet. 克罗克福德将Y组合器称为“ ...计算机科学中最奇怪,最奇妙的工件之一” ,但是我认为这是因为他不理解它,并且继续复制/粘贴您在互联网上看到的相同的Y示例。 There's nothing strange about it unless you write it in such a way that obfuscates all meaning - 除非您以混淆所有含义的方式编写它,否则它就没有什么奇怪的 -

// 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))

Which matches our version of Y above - 与我们上面的Y版本相匹配-

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

without U 没有你

We can expand U such that Y has a standalone definition - 我们可以扩展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)))

We're back to an IIFE, but this time our definition more closely matches Haskell Curry's original definition - 我们回到了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 


expanding our Y intuition 扩大我们的Y直觉

Like almost all other Y tutorials out there, Crockford demonstrates using Y on a function with a single argument. 像几乎所有其他Y教程一样,Crockford演示了如何在具有单个参数的函数上使用Y There's a lot of potential lurking just beyond these preliminary understandings of Y . 除了对Y这些初步理解之外,还有很多潜在的潜伏性。 I'll show you simple functions that take 2 or even 3 arguments - 我将向您展示带有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 

There's really no limit to the complexity of program that Y can express - 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