簡體   English   中英

類似的咖喱功能產生不同的結果

[英]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中使用的fmatch(/q/i) ,因此當Array.prototype.filter調用它時,您將獲得三(3)個額外的參數,而不是預期的一(1)個。 curry的上下文中,這意味着a.length將為四(4),並且由於4 === fn.lengthfalse (其中fn.length2 ),因此返回的值為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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM