[英]Similar curry functions producing different results
我正在學習函數式javascript,並且遇到了curry函數的兩個不同實現。 我試圖了解兩者之間的區別,它們看起來相似,但在某些情況下工作不正確,而在其他情況下工作正確。
我嘗試過互換使用es6'const'定義的函數,這種情況適用於簡單情況,但是當使用'filter'過濾字符串時,結果不正確,但使用整數可以產生所需的結果。
//es6
//Does not work well with filter when filtering strings
//but works correctly with numbers
const curry = (fn, initialArgs=[]) => (
(...args) => (
a => a.length === fn.length ? fn(...a) : curry(fn, a)
)([...initialArgs, ...args])
);
//Regular js
//Works well for all cases
function curry(fn) {
const arity = fn.length;
return function $curry(...args) {
if (args.length < arity) {
return $curry.bind(null, ...args);
}
return fn.call(null, ...args);
};
}
const match = curry((pattern, s) => s.match(pattern));
const filter = curry((f, xs) => xs.filter(f));
const hasQs = match(/q/i);
const filterWithQs = filter(hasQs);
console.log(filterWithQs(["hello", "quick", "sand", "qwerty", "quack"]));
//Output:
//es6:
[ 'hello', 'quick', 'sand', 'qwerty', 'quack' ]
//regular:
[ 'quick', 'qwerty', 'quack' ]
如果您更改filter
以使用xs.filter(x => f(x))
而不是xs.filter(f)
,它將起作用-
const filter = curry((f, xs) => xs.filter(x => f(x)))
// ...
console.log(filterWithQs(["hello", "quick", "sand", "qwerty", "quack"]))
// => [ 'quick', 'qwerty', 'quack' ]
這樣做的原因是因為Array.prototype.filter將三 (3)個參數傳遞給“回調”函數,
callback
函數是一個謂詞,用於測試數組的每個元素。 返回true保留元素,否則返回false。 它接受三個參數:
element
數組中正在處理的當前元素。index
(可選)-數組中正在處理的當前元素的索引。array
(可選)-調用了數組過濾器。
在filter
中使用的f
是match(/q/i)
,因此當Array.prototype.filter
調用它時,您將獲得三(3)個額外的參數,而不是預期的一(1)個。 在curry
的上下文中,這意味着a.length
將為四(4),並且由於4 === fn.length
為false
(其中fn.length
為2
),因此返回的值為curry(fn, a)
,這是另一個功能。 由於在JavaScript中所有函數都被視為真實值,因此filter
調用返回所有輸入字符串。
// your original code:
xs.filter(f)
// is equivalent to:
xs.filter((elem, index, arr) => f(elem, index, arr))
通過將filter更改為使用...filter(x => f(x))
,我們僅允許將一(1)參數傳遞給回調,因此curry
將計算2 === 2
,這是true
,並且返回值是評估match
的結果,該結果返回預期的true
或 false
。
// the updated code:
xs.filter(x => f(x))
// is equivalent to:
xs.filter((elem, index, arr) => f(elem))
另一種可能更好的選擇是在您的“ es6” curry
中將===
更改為>=
-
const curry = (fn, initialArgs=[]) => (
(...args) => (
a => a.length >= fn.length ? fn(...a) : curry(fn, a)
)([...initialArgs, ...args])
)
// ...
console.log(filterWithQs(["hello", "quick", "sand", "qwerty", "quack"]))
// => [ 'quick', 'qwerty', 'quack' ]
這使您可以“正常”“溢出”函數參數,而JavaScript對此沒有問題-
const foo = (a, b, c) => // has only three (3) parameters console.log(a + b + c) foo(1,2,3,4,5) // called with five (5) args // still works // => 6
最后,這是我過去寫curry
其他方式。 我已經測試過它們每個都能為您的問題產生正確的輸出-
通過輔助循環-
const curry = f => {
const aux = (n, xs) =>
n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
return aux (f.length, [])
}
多功能curryN
,具有可變參數功能-
const curryN = n => f => {
const aux = (n, xs) =>
n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
return aux (n, [])
};
// curry derived from curryN
const curry = f => curryN (f.length) (f)
傳播數天-
const curry = (f, ...xs) => (...ys) =>
f.length > xs.length + ys.length
? curry (f, ...xs, ...ys)
: f (...xs, ...ys)
向lambda微積分和Howard Curry的定點Y組合器致敬-
const U =
f => f (f)
const Y =
U (h => f => f (x => U (h) (f) (x)))
const curryN =
Y (h => xs => n => f =>
n === 0
? f (...xs)
: x => h ([...xs, x]) (n - 1) (f)
) ([])
const curry = f =>
curryN (f.length) (f)
和我個人的最愛-
// for binary (2-arity) functions
const curry2 = f => x => y => f (x, y)
// for ternary (3-arity) functions
const curry3 = f => x => y => z => f (x, y, z)
// for arbitrary arity
const partial = (f, ...xs) => (...ys) => f (...xs, ...ys)
最后,對@Donat的答案進行了有趣的修改,可以啟用匿名遞歸-
const U =
f => f (f)
const curry = fn =>
U (r => (...args) =>
args.length < fn.length
? U (r) .bind (null, ...args)
: fn (...args)
)
此處的主要區別不是es6語法,而是參數如何部分應用於函數。
第一版: curry(fn, a)
第二版: $curry.bind(null, ...args)
如果您將第一個版本(es6)更改為fn.bind(null, ...args)
,則僅適用於一步一步(如示例所示fn.bind(null, ...args)
es6語法中的“ Regular js”版本表示形式如下所示(您需要在遞歸調用中使用常量來為函數命名):
curry = (fn) => {
const c = (...args) => (
args.length < fn.length ? c.bind(null, ...args) : fn(...args)
);
return c;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.