[英]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.