Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
729 views
in Technique[技术] by (71.8m points)

haskell - How to correctly curry a function in JavaScript?

I wrote a simple curry function in JavaScript which works correctly for most cases:

const add = curry((a, b, c) => a + b + c);

const add2 = add(2);

const add5 = add2(3);

console.log(add5(5));
<script>
const curried = Symbol("curried");

Object.defineProperty(curry, curried, { value: true });

function curry(functor, ...initArgs) {
    if (arguments.length === 0) return curry;

    if (typeof functor !== "function") {
        const value = JSON.stringify(functor);
        throw new TypeError(`${value} is not a function`);
    }

    if (functor[curried] || initArgs.length >= functor.length)
        return functor(...initArgs);

    const result = (...restArgs) => curry(functor, ...initArgs, ...restArgs);

    return Object.defineProperty(result, curried, { value: true });
}
</script>
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

@Aadit,

I'm posting this because you shared a comment on my answer to To “combine” functions in javascript in a functional way? I didn't specifically cover currying in that post because it's a very contentious topic and not really a can of worms I wanted to open there.

I'd be wary using the phrasing "how to correctly curry" when you seem to be adding your own sugar and conveniences into your implementation.

Anyway, all of that aside, I truly don't intend for this to be an argumentative/combative post. I'd like to be able to have an open, friendly discussion about currying in JavaScript while emphasizing some of the differences between our approaches.

Without further ado...


To clarify:

Given f is a function and f.length is n. Let curry(f) be g. We call g with m arguments. What should happen? You say:

  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.

Let's see a code example of what @Aadit M Shah's code actually does

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

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

There are two things happening here:

  1. You're attempting to support calling curried functions with variadic arguments.
  2. You're automatically currying returned functions

I don't believe there's a lot of room for debate here, but people seem to miss what currying actually is

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...

I'm bolding that last bit, because it's so important; each function in the sequence only takes a single argument; not variadic (0, 1, or more) arguments like you suggest.

You mention haskell in your post, too, so I assume you know that Haskell has no such thing as functions that take more than one argument. (Note: a function that takes a tuple is still just a function that takes one argument, a single tuple). The reasons for this are profound and afford you a flexibility in expressiveness not afforded to you by functions with variadic arguments.

So let's re-ask that original question: What should happen?

Well, it's simple when each function only accepts 1 argument. At any time, if more than 1 argument is given, they're just dropped.

function id(x) {
  return x;
}

What happens when we call id(1,2,3,4)? Of course we only get the 1 back and 2,3,4 are completely disregarded. This is:

  1. how JavaScript works
  2. how Wikipedia says currying should work
  3. how we should implement our own curry solution

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

currying technique à la naomik

In this approach, we write a curry function that continuously returns single-parameter functions until all arguments have been specified

As a result of this implementation we have 6 multi-purpose functions.

// 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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...