Skip to content
This repository was archived by the owner on Nov 23, 2017. It is now read-only.

Currying #175

Open
puffnfresh opened this issue Apr 4, 2013 · 23 comments
Open

Currying #175

puffnfresh opened this issue Apr 4, 2013 · 23 comments
Labels

Comments

@puffnfresh
Copy link
Owner

One of the goals for Roy is to ensure the JavaScript output should easily be used from plain JavaScript (and hopefully other languages).

Here we want f to be a curried function:

let f a b = a + b
let g = f 42

The problem with currying each function definition is that it doesn't compile to great JavaScript:

function f(a) {
    return function(b) {
        return a + b;
    };
}
var g = f(42);

But maybe we could implement currying on the caller side?

function f(a, b) {
    return a + b;
}
function g(b) {
    return f(42, b);
}

Yes, the JavaScript it not great - but only if you use the currying. Plain JavaScript clients don't change.

@kmarekspartz
Copy link

I would expect the following:

f (a, b) = a + b
g a b = a + b

to become:

var f = function(a, b) {
    return a + b;
};

var g = function(a) {
    return function (b) {
         return a + b;
    };
};

This would allow you to still use the tupled functions when you need to for compatibility. We can then have a functions to convert between curryable and tupled functions:

curry2 f a b = f (a, b)
curry3 f a b c = f (a, b, c)
etc.

uncurry2 f (a, b) = f a b
etc.

---->

var curry2 = function (f) {
    return function (a) {
        return function (b) {
              return f(a,b);
        };
    };
};
etc.

var uncurry2 = function (f) {
    return function (a, b) {
         return f(a)(b);
    };
};
etc.

@joneshf
Copy link
Collaborator

joneshf commented Apr 4, 2013

@pufuwozu sorry, I don't really understand that last example.

@puffnfresh
Copy link
Owner Author

@joneshf sorry, that made no sense. Just updated it with what I meant.

@kmarekspartz
Copy link

By this logic, lambdas could be lifted, too.

f a b = \x -> a + x

g b = f 12 b 12

--->

function f (a, b, x) {
    return a + x;
};

function g (b) {
    return f(12, b, 12);
};

But I'm not sure that is a good idea.

@joneshf
Copy link
Collaborator

joneshf commented Apr 22, 2013

@zeckalpha I kind of like the idea of a curry function, though I think we can generalize it.

var curry = function(f) {
  function currier(curriedArgs) {
    // We need at least as many args as the function takes.
    if (curriedArgs.length >= f.length) {
      // Pass them all into the function.
      // We could only pass as many as needed,
      // but some goof could be using unnamed args like this is.
      return f.apply(this, curriedArgs);
    } else {
      // Not enough args, let's curry it with what ever else comes down the pipe later
      return function() {
        var newArgs = curriedArgs.concat(Array.prototype.slice.call(arguments));
        return currier(newArgs);
      };
    }
  }
  // Grab only the arguments.
  return currier(Array.prototype.slice.call(arguments, 1));
};

Something like that, except with more clarity.

So we should be able to do things like:

let f x y = x + y
let g = f 37
console.log (g 3)

and have it compile to

var f = function(x, y) {
  return x + y;
};
var g = curry(f, 37);
console.log(g(3));

var curry = function(f) {
  function currier(curriedArgs) {
    if (curriedArgs.length >= f.length)
      return f.apply(this, curriedArgs);
    return function() {
      return currier(curriedArgs.concat(Array.prototype.slice.call(arguments)));
    };
  }
  return currier(Array.prototype.slice.call(arguments, 1));
};

@kmarekspartz
Copy link

Perhaps a better name for this function would be partial since it isn't currying or uncurrying, but rather using partial application and returning a closure for the remaining arguments.

@joneshf
Copy link
Collaborator

joneshf commented May 4, 2013

Ah, thanks for the correction.

So I guess the question is, can a generalized curry function be written that is readable? Or do we cut our losses and just say partial application is good enough? How would a plain js developer do it? Would they write the partial, or try for true currying?

@kmarekspartz
Copy link

I'm not really one, so I'm not sure exactly, but I don't think they would do it! The only use cases I can think of for partial application use an object passed in instead, e.g. in Underscore, the where method on collections.

@rtfeldman
Copy link
Collaborator

+1 for the original "caller side" proposal that generates the following for g:

function g(b) {
    return f(42, b);
}

If exported Roy functions are curried, calling Roy libraries from vanilla JS will entail royFunc(arg1)(arg2)(arg3) instead of the usual royFunc(arg1, arg2, arg3). Then you must remember to use that calling style only for libraries written in Roy, making Roy libraries intrinsically inconvenient to vanilla JS users.

This is an especially important consideration for people wanting to incorporate Roy into existing codebases incrementally: rewrite one piece of legacy code in Roy, then another, then another...

If Roy functions require a different calling style, then implementing drop-in rewrites of individual JS functions or modules is off the table.

@kmarekspartz
Copy link

Can't compatibility be managed using tupled arguments?

f (a, b) = a + b

g b = f (42, b)

@puffnfresh
Copy link
Owner Author

@zeckalpha Scala does something like that:

def f(a: (Int, Boolean), b: String) = ???
def g(a: (Int, Boolean))(b: String) = ???

(I used a tuple as an argument type just to show that you can still have that)

@rtfeldman
Copy link
Collaborator

Tupled arguments are one approach to compatibility, but it means Roy authors have to consciously code with compatibility in mind.

As a Roy author I'd rather just code naturally and have the compiler take care of generating JS-friendly code.

@kmarekspartz
Copy link

I like the ability to have both tupled and curried functions in ML and Haskell. I found the curried notation and tupled semantics confusing coming to Roy.

@puffnfresh
Copy link
Owner Author

@zeckalpha you can still have tupled arguments but they're not going to compile to a multiple argument JavaScript call. It'd have to be a tuple object.

(Just like Haskell)

@rtfeldman
Copy link
Collaborator

Yeah, that's how I would expect it to work from the JS side.

@puffnfresh
Copy link
Owner Author

A pollyfilled Function.prototype.bind (pollyfined for IE6) would actually be more normal to see in plain JavaScript code. That could be a good approach to "caller-side" currying.

@rtfeldman
Copy link
Collaborator

That works too. As an aside I like the idea of saying "Roy targets ES5, so include these polyfill libraries if you need to target lower than that", or include something along the lines of a --polyfills compiler flag to allow you to opt into (or maybe opt out of) having polyfills included in the generated source.

The most important part to me is getting the best of both worlds: automatic currying when I'm writing Roy, and standard invocation style when I'm calling Roy functions from JS.

@puffnfresh
Copy link
Owner Author

CoffeeScript used to do this when you used its "fat arrow" (now it's smart enough to just alias this):

var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

I think that's a good default. Roy should probably have an ES5 mode to allow you to not have that and just rely on a builtin Function.prototype.bind but that can come much later.

@rtfeldman
Copy link
Collaborator

Sure, that's definitely a minor optimization.

@NickHeiner
Copy link

👍

@X1011
Copy link

X1011 commented Sep 10, 2013

@joneshf, I'm assuming by "plain JS developer" you mean a developer who writes plain JS in a functional style.
ramda.js, @DrBoolean's libraries, and prelude.ls all take your approach and make the functions they export partially applicable, and they all call it currying. DrBoolean's implementation comes straight from wu.js, and prelude.ls takes advantage of LiveScript's built-in implementation.
I've not seen any functional JS libraries that do honest-to-goodness, one-function-per-argument currying by default.

@joneshf
Copy link
Collaborator

joneshf commented Sep 10, 2013

I haven't looked at it too indepth, but underscore-contrib seems to actually do this: https://github.com/documentcloud/underscore-contrib/blob/master/underscore.function.arity.js#L127-L176 Based on https://github.com/eborden/js-curry But yeah, it seems like it's not the norm.

cassiemeharry added a commit that referenced this issue Nov 28, 2013
Functions definitions are compiled to an un-curried form, and function
calls act accordingly. Also, not calling a function with enough
arguments now generates a wrapping function.

    let f x y = x + y
    let g = f 1
    console.log (g 2)

    // The above Roy compiles into this
    var f = function (x, y) {
        return x + y;
    }
    var g = function (_1) {
        return f(1, _1);
    }
    console.log(g(2));

Resolves #175
@joneshf
Copy link
Collaborator

joneshf commented Nov 28, 2013

Very nice @nickmeharry! Can't wait for constraints to land.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

6 participants