簡體   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