Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reconsider currying #6

Closed
rpominov opened this issue Apr 23, 2016 · 17 comments
Closed

Reconsider currying #6

rpominov opened this issue Apr 23, 2016 · 17 comments

Comments

@rpominov
Copy link
Member

rpominov commented Apr 23, 2016

The spec currently require that all methods of each type must be curried. Which is cool, but might be problematic sometimes.

Some problems are:

  • It might affect performance
  • It makes call stack ugly which is a problem when debugging etc.
  • One have to depend on a module that provides a curry function
  • We can't have optional arguments in a curried function

We could change the spec in a way that it won't require methods to be curried, but also won't require otherwise, so people could choose to use currying or not by themselves.

Related: #2

@rpominov
Copy link
Member Author

Another question we should ask ourselves "Is point free actually a good thing?". Take a look at the example:

foo(T.bar(x))

foo(y => T.bar(x, y))

In the second line one can tell much more about what is going on by just looking at the line itself, while to understand the first line they will need to check docs, other files, etc.

@rpominov rpominov self-assigned this May 31, 2016
@rpominov
Copy link
Member Author

rpominov commented Jun 3, 2016

Plan

  • Remove note from spec.md
  • Rewrite type signatures withou currying
  • Make sure we still allow currying (not require, but allow)
  • Rewrite built-in types and derive utils without currying
  • Remove utils for currying

@ivenmarquardt
Copy link

ivenmarquardt commented Oct 6, 2016

@rpominov Currying and function composition are fundamental concepts of FP. Being point-free is just one property of them. static land makes this unnecessary and thus should embrace function composition, which is greatly facilitated by currying. Functions in Haskell are curried by default and isn't fantasy/static land the attempt to transfer Haskell idioms to Javascript?

  • It might affect performance

I guess this is merely micro-optimization. If you encounter serious performance penalties in a particular portion of your code, optimize that portion.

  • it makes call stack ugly which is a problem when debugging etc.

Good point.

  • One have to depend on a module that provides a curry function

Arrow functions provide a succinct syntax to manually define curried functions. No need for a programmatic solution.

  • We can't have optional arguments in a curried function

Optional arguments are evil, just like variadic functions. With curried functions you simply don't need them:

const f = opt => x => y => ...;
const g = f("whatsoever");

Would you re-reconsider currying?

@rpominov
Copy link
Member Author

rpominov commented Oct 6, 2016

Before this issue, when currying was in the spec, it was required for all methods to support both T.foo(a)(b) and T.foo(a, b) modes (Ramda-like currying). So arguments I was making were about no currying on one hand and Ramda-like currying on the other.

You suggest a bit different approach to support only T.foo(a)(b). In this case the arguments against currying are indeed weaker. But we will (need)(to)(do)(this) all the time. So not sure...

@rpominov
Copy link
Member Author

rpominov commented Oct 6, 2016

And this can really hurt performance actually. Consider some function like fold that accepts a static-land Monoid. If it called on a collection with a lot of items and instead of T.concat(a, b) we're doing T.concat(a)(b), creating a temporary function on each step, the performance hit can be huge.

@rpominov
Copy link
Member Author

rpominov commented Oct 6, 2016

Another idea. Currying can be implemented as a library function that accepts a normal (current spec) static-land dictionary and returns a new one with curried methods. This way people could enjoy currying for any SL type without spec requiring it.

@ivenmarquardt
Copy link

ivenmarquardt commented Oct 7, 2016

First of all I think that the approach static land specifies will hit the big-time. I've played around a bit with an implementation and it actually solved problems and simplified code. Apart from the verbosity I haven't been faced with any drawbacks yet.

Back to the topic: I don't want to be dogmatic here. My experience is that whenever I have an uncurried function in my toolset, I sooner or later re-implement it as a curried version. Also I've never experienced performance hits caused by currying so far.

On the other hand I cannot assess the impact of currying on the spec or on corresponding implementations. Does it really matter? I would say yes, but that is rather intuitively. I need more experience.

Your last suggestion sounds promising. Let's wait and see if currying is also an issue for others.

@rpominov
Copy link
Member Author

rpominov commented Oct 8, 2016

Sounds good, @ivenmarquardt , let's wait for more feedback.

Just one more option I want to mention, we always can use a helper like partial e.g., lifted = partial(T.map, f). I probably like this the most because it's explicit.

@jgrund
Copy link

jgrund commented Oct 11, 2016

Like @ivenmarquardt, I also find myself currying functions as I am a proponent of the compositional / point-free style.

Like anything, I think there is a balance to be struck when using point-free in regards to readability.

@jgrund
Copy link

jgrund commented Oct 11, 2016

Interesting article on stack-trace / debugging:

https://medium.com/@drboolean/debugging-functional-7deb4688a08c#.7fv7ks3xn

@masaeedu
Copy link

masaeedu commented Apr 5, 2018

@rpominov Re; (need)(to)(do)(this), Sanctuary recently changed all their APIs to simple currying, and they find that appropriate (use) (of) (whitespace) makes things look OK despite the noisier function application syntax in JS. See sanctuary-js/sanctuary#438

Additionally, JS now has a proposal for the reverse application |> operator, which plays really nicely with curried, static functions:

  inputObservable
    |> Obs.map(x => x * 2)
    |> Obs.flatMap(x => Obs.delay(1000, x))
    |> Obs.filter(x => x < 50)
    |> Obs.scan(x => y => x + y)

@ivenmarquardt
Copy link

ivenmarquardt commented Apr 6, 2018

@masaeedu Btw., you can have function composition with arbitrary number of curried functions along with simple recursion:

// right to left
const comp = f => Object.assign(g =>
  comp(x =>
    f(g(x))),
    {runComp: f});

// left to right
const pipe = f => Object.assign(g =>
  pipe(x =>
    g(f(x))),
    {runPipe: f});

const inc = n => n + 1;
const sqr = n => n * n;

comp(sqr)
  (inc)
    (inc)
      (inc)
        (inc)
          .runComp(0); // 16

I prefer this recursive encoding over the spread operator (e.g. pipe(sqr, inc, inc, ...), because parentheses indicate function application more naturally. You can encode applicative/monadic function structures as well. The Lisp style code indentation is merely my personal style.

@rpominov
Copy link
Member Author

rpominov commented Apr 6, 2018

I still feel like forcing everyone to call functions in weird way is not a good idea. I know many people are okay with it, but probably many will not be. Also performance issue still looks like a real issue to me. It's true that in most cases it won't matter, but if a curried function call appears in a hot path it will be a problem. I hope https://github.com/tc39/proposal-partial-application lands soon, seems like it solves all problems.

@gabejohnson
Copy link
Member

gabejohnson commented Apr 6, 2018

I still feel like forcing everyone to call functions in weird way is not a good idea

Since the introduction of arrow functions, manually defining curried functions has become easy, and the only way to call

const K = x => _ => x;

is

K(42)(null);
// or
K (42) (null);

I'll admit it took me a little getting used to (a couple of days), but we're talking about a different programming paradigm here; the difference in calling patterns emphasizes this.

@davidchambers
Copy link
Member

Spaces are crucial. This expression would be difficult to read without spaces:

B (B (B (B (eitherToMaybe)))) (encaseEither3 (I))

@RaoulSchaffranek
Copy link

I don't want to start this discussion all over. I was just wondering if curried functions still conform to the spec as it was suggested by the original poster. The only hint in the specification is implicit in the section about module-signatures, where a => b => c seems to be different from (a,b) => c, so my guess is, curried functions do not conform anymore, am I correct?

@rpominov
Copy link
Member Author

@RaoulSchaffranek You can define a function in a way that both fn(a, b) and fn(a)(b) would work. In this case it will be compatible, since the specification only requires fn(a, b) to work.

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

No branches or pull requests

7 participants