如何正确咖喱JavaScript中的函数?

我在 JavaScript中写了一个简单的咖喱功能,在大多数情况下都能正常工作:
var add = curry(function (a,b,c) {
    return a + b + c;
});

var add2 = add(2);

var add5 = add2(3);

alert(add5(5));
<script>
function curry(f) {
    var length = f.length;

    if (length > 0) {
        return partial(f,length,[]);
    } else {
        return f; // f is already curried
    }
}

function partial(f,a) {
    return function () {
        var arity = length;
        var count = arguments.length;
        var args  = new Array(count);
        var index = 0;

        while (index < count) {
            args[index] = arguments[index++];
        }

        args = a.concat(args);

        return count < arity ?
            partial(f,arity - count,args) :
            f.apply(this,args);
    };
}
</script>

但是,它不适用于以下情况:

// length :: [a] -> Number

function length(a) {
    return a.length;
}

// filter :: (a -> Bool) -> [a] -> [a]

var filter = curry(function (f,a) {
    return a.filter(f);
});

// compose :: (b -> c) -> (a -> b) -> a -> c

var compose = curry(function (f,g,x) {
    return f(g(x));
});

// countWhere :: (a -> Bool) -> [a] -> Number

var countWhere = compose(compose(length),filter);

根据以下问题countWhere被定义为(length.).过滤:

What does (f .) . g mean in Haskell?

所以我应该可以使用countWhere如下:

countWhere(odd,[1,2,3,4,5]);

function odd(n) {
    return n % 2 === 1;
}

但是,不返回3(数组[1,5]的长度),它返回一个函数.我究竟做错了什么?

解决方法

@Aadit,

我发布这是因为你分享了我对To “combine” functions in javascript in a functional way?的答案的评论我没有专门涵盖在那篇文章中的currying,因为它是一个非常有争议的话题,而不是真正的一个蠕虫病毒,我想打开那里.

当您似乎将自己的糖和便利添加到您的实施中时,我会谨慎使用“如何正确咖喱”的措辞.

无论如何,除此之外,我真的不打算这样做是一个辩论/辩护的职位.我想要能够在JavaScript中进行开放友好的讨论,同时强调我们的方法间的一些差异.

无需再费周折…

澄清:

给定f是一个函数,f.length是n.咖喱(f)是g.我们用m个参数调用g.应该怎么办你说:

  1. If m === 0 then just return g.
  2. If m < n then partially apply f to the m new arguments,and return a new curried function which accepts the remaining n - m arguments.
  3. If m === n then apply f to the m arguments. If the result is a function then curry the result. Finally,return the result.
  4. If m > n then apply f to the first n arguments. If the result is a function then curry the result. Finally,apply the result to the remaining m - n arguments and return the new result.

让我们来看看@Aadit M Shah代码实际做的代码示例

var add = curry(function(x,y) {
  return function(a,b) {
    return x + y + a + b;
  }
});

var z = add(1,3);
console.log(z(4)); // 10

这里有两件事情:

>你试图用可变参数支持调用curried函数.
>你自动调用返回函数

我不相信在这里有很多的辩论空间,但是人们似乎错过了currying的实际

via: Wikipedia
In mathematics and computer science,currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions,each with a single argument

我最大胆的是,因为它是如此重要;序列中的每个函数只需要一个参数;不像你所建议的那样(0,1或更多)参数.

你也可以在你的帖子中提到haskell,所以我假设你知道Haskell没有这样的东西,这个功能需要多个参数. (注意:一个需要元组函数只是一个需要一个参数的函数,一个单独的元组).这样做的原因是深刻的,并且为您提供不具有可变参数的功能给您的表现力的灵活性.

所以让我们再问一下原来的问题:应该怎么办?

那么,每个函数只接受1个参数就很简单.在任何时候,如果提供超过1个参数,它们就被删除.

function id(x) {
  return x;
}

当我们调用id(1,4)时会发生什么?当然我们只得到1回,4完全被忽视.这是:

> JavaScript如何工作
维基百科说维权应该如何工作
>我们应该如何实施我们自己的咖喱解决方

Before we go further,I’m going to use ES6-style 07003 but I will also include the ES5 equivalent at the bottom of this post. (Probably later tonight.)

currying技术àla naomik

在这方法中,我们编写一个咖喱功能,连续返回单参数函数,直到指定了所有参数为止

作为这个实现的结果,我们有6个多用途功能.

// no nonsense curry
const curry = f => {
  const aux = (n,xs) =>
    n === 0 ? f (...xs) : x => aux (n - 1,[...xs,x])
  return aux (f.length,[])
}
   
// demo
let sum3 = curry(function(x,y,z) {
  return x + y + z;
});
    
console.log (sum3 (3) (5) (-1)); // 7

好的,所以我们看到一个使用简单的辅助循环实现的咖喱技术.它没有依赖关系和一个低于5行代码的声明性定义.它允许功能部分应用,一次有1个参数,就像一个咖喱功能应该工作.

没有魔法,没有意想不到的自动调理,没有其他意外的后果.

但是呢,还有什么真正的意义呢?

那么,事实证明,我真的不是咖喱我写的功能.如下所示,我通常以咖喱形式定义所有可重用的功能.所以真的,你只需要咖喱,当你想与一些你没有控制的功能接口,也许来自一个lib或某些东西;其中一些可能有可变的界面!

我现在咖喱N

// the more versatile,curryN
const curryN = n => f => {
  const aux = (n,x])
  return aux (n,[])
};

// curry derived from curryN
const curry = f => curryN (f.length) (f);

// some caveman function
let sumN = function() {
  return [].slice.call(arguments).reduce(function(a,b) {
    return a + b;
  });
};

// curry a fixed number of arguments
let g = curryN (5) (sumN);
console.log (g (1) (2) (3) (4) (5)); // 15

要咖喱还是不咖喱?就是那个问题

我们会写一些例子,我们的功能都是以咖喱的形式出现的.功能将保持极其简单.每个都有1个参数,每个都有一个返回表达式.

// composing two functions
const comp = f => g => x => f (g (x))
const mod  = y => x => x % y
const eq   = y => x => x === y
const odd  = comp (eq (1)) (mod (2))

console.log (odd(1)) // true
console.log (odd(2)) // false

你的countWhere功能

// comp :: (b -> c) -> (a -> b) -> (a -> c)
const comp = f => g => x =>
  f(g(x))

// mod :: Int -> Int -> Int
const mod = x => y =>
  y % x

// type Comparable = Number | String
// eq :: Comparable -> Comparable -> Boolean
const eq = x => y =>
  y === x

// odd :: Int -> Boolean
const odd =
  comp (eq(1)) (mod(2))

// reduce :: (b -> a -> b) -> b -> ([a]) -> b
const reduce = f => y => ([x,...xs]) =>
  x === undefined ? y : reduce (f) (f(y)(x)) (xs)

// filter :: (a -> Boolean) -> [a] -> [a]
const filter = f =>
  reduce (acc => x => f (x) ? [...acc,x] : acc) ([])

// length :: [a] -> Int
const length = x =>
  x.length

// countWhere :: (a -> Boolean) -> [a] -> Int
const countWhere = f =>
  comp (length) (filter(f));

console.log (countWhere (odd) ([1,5]))
// 3

备注

那么咖喱还是不咖喱?

// to curry
const add3 = curry((a,c) =>
  a + b + c
)

// not to curry
const add3 = a => b => c =>
 a + b + c

使用ES6箭头功能成为当今JavaScripter的选择,我认为手动咖喱功能的选择是一个没有意义的选择.它实际上更短,只有以咖喱形式写出来的开销较少.

也就是说,您仍然要与不提供公开表现形式的libs进行接口.对于这种情况,我建议

咖喱咖喱N(上文定义)
>部分(如defined here)

@Iven,

你的咖喱N实现非常好本节仅供您使用.

const U = f=> f (f)
const Y = U (h=> f=> f(x=> h (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)

const add3 = curry ((x,z)=> x + y + z)

console .log (add3 (3) (6) (9))

相关文章

前言 做过web项目开发的人对layer弹层组件肯定不陌生,作为l...
前言 前端表单校验是过滤无效数据、假数据、有毒数据的第一步...
前言 图片上传是web项目常见的需求,我基于之前的博客的代码...
前言 导出Excel文件这个功能,通常都是在后端实现返回前端一...
前言 众所周知,js是单线程的,从上往下,从左往右依次执行,...
前言 项目开发中,我们可能会碰到这样的需求:select标签,禁...