-
-
Save getify/c8f017cc45246c723af7fd1b0b6af496 to your computer and use it in GitHub Desktop.
Proposal: curried function declarations in javascript -- aka, making FP development in JS much much nicer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Standard: | |
function fn(x) { | |
return function(y){ | |
return function(z){ | |
return x * y / z; | |
}; | |
}; | |
} | |
// pros: function `fn()` is a regular function declaration, hoists | |
// cons: quite verbose, requires intermediate functions (often anonymous), | |
// can't do "loose currying" where you pass in more than one arg in a single | |
// call (like `fn(1,2)(3)`), JS engine probably can't inline/optimize away | |
// these intermediate functions as a result |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Preferred: | |
var fn = x => y => z => x * y / z; | |
// pros: much shorter and thus a bit more visually attractive | |
// cons: doesn't hoist, all functions are anonymous (lexically)... | |
// even `fn()` -- it has the inferred name `fn`, but it's still | |
// lexically anonymous so it can't reliably self-reference itself, etc, | |
// some think having to essentially back-track or read right-to-left to | |
// parse arrow functions (parameters vs expression bodies) is less | |
// readable, same problems with "loose currying" and JS engine optimizations | |
// from previous snippet |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Proposed: | |
function fn(x)(y)(z) { | |
return x * y / z; | |
} | |
// pros: real function declaration, still hoistable, would allow engine to | |
// support "loose currying" (like `fn(1,2)(3)`), and engine can more possibly | |
// optimize intermediate functions when they're not necessary, intermediate | |
// functions (when necessary) could have inferred names, like `fn@2` | |
// or `fn:2` or `fn#2`, sort of like bound methods (that have been partially | |
// applied), OR the "intermediate" function could actually just be the original | |
// `fn` and not a different one, but where JS is collecting its arguments, | |
// engine could maintain the `this` binding across all the function calls | |
// automatically | |
// cons: maybe a bit more complex of JS grammar for the parser, little more | |
// verbose in needing the (..)s to disambiguate syntax, intermediate functions | |
// still would be lexically anonymous |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// curried functions could still be expressions, even anonymous or arrows: | |
foo( function fn(x)(y)(z){ return x * y / z; } ); | |
foo( function(x)(y)(z){ return x * y / z; } ); | |
foo( (x)(y)(z) => x * y / z ); | |
// and still be concise methods/class methods/etc: | |
var o = { | |
fn(x)(y)(z) { return x * y / z; } | |
}; | |
// and still support multiple parameters at each level if you want: | |
function fn(x,y)(z) { return x * y / z; } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Some clarification around this: AFAICT, all implementations of curry in FP-in-JS libraries assume that if you explicitly say you are currying 3 arguments, it needs 3 explicit arguments, no skipping allowed. If you curry a function of arity 3 and two of them have "defaults", you can't just pass one explicit argument and expect curry to understand that and apply the defaults for the other two.
Furthermore, even if a
curry(..)
could be that smart, that creates ambiguity, because... what if I don't want to invoke the default? How do I do that?In other words, automatic defaults are incompatible with explicit-N currying in JS. That doesn't mean you can't do defaults, it means that to trigger a default, you have to pass an explicit
undefined
in that argument position. Check this assertion against Ramda, lodash/fp, etc, and I think you'll see what I mean.For example, Ramda's
curryN(..)
explicit-arity currying:Same goes for lodash/fp's
curryN(..)
.But unfortunately Ramda's
curry(..)
surprisingly just assumes whateverfn.length
reports, which is botched/unreliable with a function likefoo(..)
above. According to spec,fn.length
stops counting in a parameter list once it encounters any non-simple parameter (like default, rest, etc).So, should we copy/mimic the behavior more like
R.curry(..)
orR.curryN(..)
?I would say emphatically the former, not the latter.
R.curry(..)
when used with functions with defaults can produce some really surprising/frustrating behavior. We should avoid anything surprising like that.R.curryN(..)
works really consistently.The only way we could copy
R.curry(..)
is if we insisted something very intrusive like, "if a parameter has a default, all other subsequent parameters also have to have a default." That would never fly, I don't think.I would propose that JS's built-in curry would simply count parameter positions (not just the way
fn.length
short-circuits), and infer that count as arity for currying. Sofoo(..)
above would be arity 3 (even though it haslength
of 1).Furthermore, you can't "skip" a parameter (like
y
) in that case. You have to pass an explicitundefined
.I feel strongly this is the least surprising (if slightly more onerous) thing that FP devs in JS would expect/understand.