Uncurried by default?

I don’t know how far you are with the v10, but maybe this is a good candidate for the experimental v11.

Personally, I can’t wait to play with it, but it might be a bit much for current production projects.

this is unlikely to land in v10, hopefully with an experimental flag

1 Like

Thought this would fit perfectly in this discussion, so I want to post it here:

Note that currying, especially with Ramda, has a very large negative performance impact.

https://news.ycombinator.com/item?id=28583319

5 Likes

The example used to demonstrate this “very large” impact in the article was a totally synthetic benchmark (a + b). I also don’t think Rambda’s dynamic curry system is really comparable to how curried functions work in ReScript.

I am on board with changing the default - most of the time it’s what I want, and losing the Curry._1 calls will simplify debugging - but I don’t see currying as having any performance impact in my app.

9 Likes

Not sure if this was a candid statement, but still an interesting tweet:

Yaron Minsky: “Hot take: currying is bad.”

2 Likes

I’m for uncurried by default. I was against it, because it makes the language less functional in the common sense. But! The convention of data-first arg almost made the whole idea of particial application questionable (killed it) in ReScript.

I don’t mind using _ to explicitly state a particial application (currying emulation) in rare cases when it is necessary. In exchange we can get better error messages, get them earlier, and make the JS interop more straightforward.

2 Likes

The thread adds interesting nuance to the hot take:

I mean, it’s cute and all that you can define multi-argument functions in terms of single-argument functions. But you can also encode tuples and integers as functions, and no one does that.

Partial application is good, of course. But the partial application afforded by currying is annoying, since you have to do the partial application in order. It would be better to have a lightweight syntax for partial application.

Currying also makes things like ownership disciplines unnecessarily complicated, and requires a bunch of cleverness to do efficiently. And it leads to worse error messages, reduces the power of the type system to catch bugs, and produces a more confusing syntax for types.

Of course, it should be possible to define curried functions. But there should be a first-class notion of multi-argument functions, and that should be the default

+1 from me!

I don’t mind using _ to explicitly state a particial application

Of course one would need a dedicated syntax to opt into partial application, but this is a good point: a bunch of cases of opting in could be handled gracefully by using _.

1 Like

I agree too, having uncurried functions by default with a syntax for partial application looks like the best of both worlds.

7 Likes

Even though I frequently use partial application in my code, I’m in full support for uncurried by default. In my opinion, the benefits of more readable JS output outweigh the downsides of a potential new explicit syntax for partial application function declaration.

2 Likes

It’s not just more readable JS output, it would greatly simplify JS interop too.

6 Likes

I’m a fan of the auto currying and it’s one of my favorite features in ReScript. I would hate to lose that.

3 Likes

The main thing is that we have two calling conventions, it has a large mental overhead. And the API is designed like this: findByU (uncurry), findBy(curry).

There seems to be clear consensus towards uncurried by default.
Notice: the auto-uncurrying language will probably still exist, the same way as the uncurried language exists today (by using dots).

9 Likes

I don’t think it’s been mentioned in this thread so far so I wanted to weigh in by reiterating my thoughts from Pipe first vs. currying

I love currying in Elm and Haskell, but that’s in large part because of its fit with pipe-last. My initial reaction to my first pipe-first language is that I feel quite a conflict between pipe first and automatic currying. If, to play well with pipe first, library functions are all designed data-first, how useful is currying in practice?

The fatal thing for me is that the two features together seem to result in a dangerous ambiguity when it comes to partial function application. Given a binary function f, f(x) means very different things whether it’s part of a pipeline or not. I’ve only been playing with the language for a few days, but I can foresee wanting to avoid the confusion (and mismatch with data-first) by always using placeholder arguments where I would ordinarily (in a pipe-last language) reach for currying.

1 Like

After async await (good news on that front) getting uncurried by default is probably the next biggest pain point for me. This will give us better interop and the chance to clean up the stlibs, confusion around pipe first / last.

In theory I would prefer curried by default, for easier functional patterns , but we’re working in a js world with js libraries so this seems like the pragmatic choice.

1 Like

I will throw my hat into the ring :tophat: as someone assessing ReScript

The situation in ReScript about autocurry vs uncurry and pipe first vs pipe last feels messy. It is messy both in the usage but also in the official libraries as there is support of uncurried and curried, as well as data first and data last.

Autocurry is nice, but then the move to data first pipe makes autocurry awkward.

My suggestion is that ReScript should not do autocurry by default.

What this means is that all functions should be uncurried by default. You could introduce a special curry syntax, but instead, we should rely on ES6 similar syntax to do currying.

For example, this:

let curry = a => b => c => a + b + c

This has three benefits:

  • It is clear that currying is going on here
  • Because types are inlined, the type signature feels like Haskell
  • ReScript today will generate performant JavaScript from this

Now the downside is JavaScript interop. If you want to export the curry function, it exports as uncurried. That may be wanted, but you might also want the opposite. That would mean we would need a @curry decorator to tell the compiler to please not inline these parameters.

I think this methodology is short and sweet (plus it aligns with modern currying in ES6)

2 Likes
7 Likes

Turn off auto formatting and you have your proposed syntax. :slight_smile:

I don’t think it is too useful to introduce a new syntax for currying specifically targeted to JS devs when the JS pipeline and partial application proposals haven’t been finalized yet.

I think making uncurried by default is… ok. I see the benefits for newcomers and interop. However I am quite used to the auto-currying and use it all the time to avoid repetition, e.g. for creating event handlers.

1 Like

For me, the issue is that it becomes confusing since functions in ReScript are syntactically defined to take many arguments. It would be easy to alleviate by requiring all arguments to be explicitly “made aware of” when currying:

let f = (a, b) => { /*....*/ }

f(1) // compiler error, missing arguments
f(1, _) // this is fine, intentional curry

I think this would also make the language friendlier to novices as well as become more readable in general. It’s not clear to me when I look at some esoteric function call whether it will be applied or won’t be.

5 Likes