curry2 :: ((a, b) -> c) -> a -> b -> c
See original GitHub issueNow that Sanctuary is close to being an alternative to Ramda rather than a library intended to be used alongside it, we should consider which Ramda functions we would miss were we to remove Ramda from our projects. R.curry
and R.curryN
are certainly important functions. def
performs currying, of course, but not every project which depends on Sanctuary also depends on sanctuary-def.
The type signatures of these Ramda functions are misleading:
curry :: (* -> a) -> (* -> a)
curryN :: Number -> (* -> a) -> (* -> a)
Accurate type signatures are an important tool for reasoning about functions. The Sanctuary functions would be much simpler:
curry2 :: ((a, b) -> c) -> a -> b -> c
curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d
curry4 :: ((a, b, c, d) -> e) -> a -> b -> c -> d -> e
curry5 :: ((a, b, c, d, e) -> f) -> a -> b -> c -> d -> e -> f
We must choose the maximum supported arity. I don’t think it’s necessary to go all the way to curry5
; curry2
and curry3
should suffice. If you call recall using R.curry
or R.curryN
to produce a function of arity greater than 3, please speak up.
🍛
Issue Analytics
- State:
- Created 7 years ago
- Reactions:1
- Comments:20 (16 by maintainers)
Top GitHub Comments
Good point, @EvanBurchard. “FP in JS” is just part of the functional programming journey, though. Ideally people will go from Underscore to Ramda to Sanctuary to PureScript to Haskell to Idris and beyond (while pursuing other wonderful approaches such as Lisp). 😄
The more concessions we make in order for Sanctuary to seem familiar to Underscore and Ramda users, the less familiar PureScript and Haskell will seem to Sanctuary users.
One day I’d love to hear a story about a team that had used Ramda, adopted Sanctuary for increased type safety, used it successfully for some time, then realized they could write the same sort of code in PureScript and thus replace their run-time type checking with compile-time type checking.
Well-defined output types are just as important to me as well-defined input types. One could argue that input-dependent output types are idiomatic in a language as dynamic as JavaScript, and there are many libraries—such as Ramda—which cater to those who agree. I have a different viewpoint. There is no compiler to scour our programs for errors, so the onus is on us as programmers to reason about the types of the values flowing through our programs. This makes it even more important to use simple functions which are explicit about arity (at the very least). It’s clear that
curry2(f)
will evaluate to a value of type* -> * -> *
. What is the shape ofR.curry(f)
? We can’t know without determining the arity off
. A Ramda-stylecurry
is ever so slightly more convenient thancurry2
, as it saves us one keystroke, but the loss of clarity is a more significant concern.Another concern is implementation complexity. The Ramda team has shown a willingness to accept complex implementations in order to provide the desired API. With complexity come edge cases. With edge cases come bugs. Again, because we have no support from a compiler it’s important that we rely on functions we can reason about. There’s a striking difference in complexity between the implementations of
R.liftN
andZ.lift2
.That’s enough for me. Let’s provide
curry2
,curry3
,curry4
, andcurry5
.I’ve done this dance many times before. I appreciate the fact that Ramda-style currying saves me from having to choose one of the following types (for some quaternary function):
f :: a -> b -> c -> d -> e
f :: a -> b -> ((c, d) -> e)
f :: a -> ((b, c) -> (d -> e))
f :: a -> ((b, c, d) -> e)
f :: (a, b) -> (c -> d -> e)
f :: (a, b) -> ((c, d) -> e)
f :: (a, b, c) -> (d -> e)
f :: (a, b, c, d) -> e
Although Ramda-style currying is at odds with my preference for well-defined types (as a function can have all the above types at once), I like to view
f(x, y)
as syntactic sugar forf(x)(y)
for some Ramda-style curried functionf
.