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

Remove ES5 era utilities that are less useful in light of arrow functions. #2755

Open
JAForbes opened this Issue Jan 6, 2019 · 12 comments

Comments

Projects
None yet
6 participants
@JAForbes
Copy link
Contributor

JAForbes commented Jan 6, 2019

This is just a wild idea I wanted to throw out in the midst of the 1.0 discussions. In a post es6 world, do we still need functions like R.prop? They feel stringly typed, they're a little harder to read in a stack trace and bulk up the library.

I've noticed when introducing Ramda in the workplace newcomers get excited and use as many Ramda functions as possible. It's usually the same or less code to write equivalent code natively with an arrow function and hopefully removing these functions would allow them to skip the initial phase of point free obsession we've all gone through.

So here's a list of functions I think we can drop if we consider the Ramda API in an es6 world.

  • R.keys (Object.keys)
  • R.values (Object.values)
  • R.add ( a => a + 1 )
  • R.subtract ( a => a - 1 )
  • R.multiply ( a => a * 10 )
  • R.or ( a => a || 'something' )
  • R.and ( a => a && 'something' )
  • R.not ( a => !a )
  • R.construct ( a => new X(a) )
  • R.divide
  • R.always ( () => x )
  • R.inc ( a => a + 1 )
  • R.dec ( a => a - 1 )
  • R.eqProps ( R.eqBy( x => x.a ) )
  • T ( () => true )
  • F ( () => false )
  • toPairs (Object.entries)
  • gt/gte ( a => a > 5 )
  • lt/lte ( a => a < 5 )
  • identity ( x => x )
  • invoker ( x => obj.f(x) )
  • modulo ( x => x % 2 )
  • nth ( xs => xs[7] )
  • objOf ( x => ({ myKey: x }) )
  • pair ( x => [x,x] )
  • pluck ( R.map( x => x.property ))
  • prop, propIs, propOr, propEq ( o => o.x ... )
  • propSatisfies ( o => o.x == 5 )
  • trim ( s => s.trim() )

Be great to hear what y'all think 👍

@tommmyy

This comment has been minimized.

Copy link
Contributor

tommmyy commented Jan 6, 2019

What I love about Ramda is actually the way how the library abstracts away implementations e.g. from above. What do you essentially need to know to write a good program in JS with R is basically just calling a function and pass arguments :) You don't need to know all methods of Object, Array, etc.

Somehow the trim expression is for me much better than s => s.trim(). Eventually I would write trim function by myself in every future app in case the Ramdists would drop it from lib.

@foxbunny

This comment has been minimized.

Copy link
Contributor

foxbunny commented Jan 6, 2019

You don't need to know all methods of Object, Array, etc.

Are you saying that... Ramda teaches you to become a bad programmer? (j/k)

@tommmyy

This comment has been minimized.

Copy link
Contributor

tommmyy commented Jan 6, 2019

no :( I am not saying that. Ramda provides unified API that's what I am saying:
curried functions with data-last paradigm.

@foxbunny

This comment has been minimized.

Copy link
Contributor

foxbunny commented Jan 6, 2019

All joking aside, I don't really see what we get from removing those and breaking pretty much all Ramda-based code in existence.

@JAForbes

This comment has been minimized.

Copy link
Contributor

JAForbes commented Jan 6, 2019

All joking aside,

😹

I don't really see what we get from removing those and breaking pretty much all Ramda-based code in existence.

Smaller surface area, less debates about the order of operator functions (R.divide), teaching when to use point free and when not to, faster load time, less to learn, less to maintain, less to document, better debugging experience/stack trace, faster load times.

As for breaking, this is a 1.0 discussion. If we're considering removing auto currying, then we can consider this.

The main function I think we should drop is R.prop. R.prop was a very useful function when we didn't have arrow functions. But these days, any function where I pass in a string for the key name instead of just writing x => x.name seems like we're sacrificing a lot for little benefit. If anything it seems less idiomatic ramda to me. We should be encouraging people to write lambdas, not avoiding it with prebuilt string based utils.

Ramda's stated philosophy, and I quote:

Using Ramda should feel much like just using JavaScript. It is practical, functional JavaScript. We're not introducing lambda expressions in strings, we're not borrowing consed lists, we're not porting over all of the Clojure functions.

Our basic data structures are plain JavaScript objects, and our usual collections are JavaScript arrays. We also keep other native features of JavaScript, such as functions as objects with properties.

There's an emphasis here on using native javascript features when it's not awkward. It used to be more awkward than it is now so I think we should take this opportunity to reframe the original stated philosophy in the current era.

What I love about Ramda is actually the way how the library abstracts away implementations e.g. from above.

I love this to, but I don't think that's enough of a justification anymore. Or at the very least it's worth considering if it is.

@GingerPlusPlus

This comment has been minimized.

Copy link
Contributor

GingerPlusPlus commented Jan 6, 2019

Glad you brought up the topic.

  • keys (Object.keys)
  • values (Object.values)
  • toPairs (Object.entries)

Yes. They bring no value, unless they're changed to work on both objects and maps (related: #2593).

  • gt/gte (a => a > 5)
  • lt/lte (a => a < 5)

No, they also handle FantasyLand's Ord.

  • pluck (R.map( x => x.property ))

#2615.

  • construct (a => new X(a))
  • invoker (x => obj.f(x))

No. Notice that when passed non-unary method name / constructor they return a curried function.
Passing arrow functions to curry isn't neat.

  • trim (s => s.trim())

I wouldn't remove functions that are (or will become) effectively just invokers, ie. curried data-last versions of functions available in the standard library. They're small, and with esm, they don't get bundled if you don't use them. In fact, I'm considering suggesting adding more of them (namely, padStart and padEnd).

That said, they could live in a separate library. But where do you draw the line? join is literally just an invoker, but it's so useful I wouldn't want it moved out of the library.

  • identity (x => x)

Subjective no. I use sortBy(identity) often, and I think it reads better than sortBy(x => x)

No strong feelings either way about the rest of functions you suggested.

My entries:

@JAForbes

This comment has been minimized.

Copy link
Contributor

JAForbes commented Jan 6, 2019

I think it's worth also considering other libraries exist now that do not have the above philosophy. Sanctuary for example is less constrained, it has no interest in replicating native features, it instead seeks to create an ecosystem that is safe from Javascript. There are other libraries that can carry that torch now.

@JAForbes

This comment has been minimized.

Copy link
Contributor

JAForbes commented Jan 6, 2019

No, they also handle FantasyLand's Ord.

Great point.

No. Notice that when passed non-unary method name / constructor they return a curried function.
Passing arrow functions to curry isn't neat.

Can you elaborate on that? I'm not sure why we're passing arrow functions to curry?

join is literally just an invoker, but it's so useful I wouldn't want it moved out of the library.

It's true we can debate where to draw the line, but at least we can agree there is a line. I personally would remove join. I think we should drop invoker/construct because we lose the stack trace at the exact point we're interacting with likely unsafe impure code.

They're small, and with esm, they don't get bundled

Great point.

I agree with your entries.

@GingerPlusPlus

This comment has been minimized.

Copy link
Contributor

GingerPlusPlus commented Jan 6, 2019

No. Notice that when passed non-unary method name / constructor they return a curried function.
Passing arrow functions to curry isn't neat.

Can you elaborate on that? I'm not sure why we're passing arrow functions to curry?

To... have them curried, obviously :P

Let's use join as an example.

const join = (sep, arr) => array.join(sep) — that's not curried.

const join = curry((sep, arr) => array.join(sep)) — that's not neat, and it's what I meant.

However, when the method is unary, you can just do const join = sep => arr => arr.join(sep), join(sep)(arr) thingy is non-issue, as it's equivalent to arr.join(sep).

And I can't find any usage of construct or constructN that can't be trivially replaced with a lambda
(traps => target => new Proxy(target, traps) instead of R.flip(R.construct(Proxy))).

I change my mind, I wouldn't miss construct(N). Unsure about invoker.

@CrossEye

This comment has been minimized.

Copy link
Member

CrossEye commented Jan 6, 2019

I'm in favor of discussing this seriously. In the strawman roadmap document I'm writing (very slowly, I'm afraid), I list a whole lot of functions that we might consider removing, mostly because modern JS makes them less compelling.

I'm not convinced, though, that we would want to remove too many of them. That prop sticks in your craw, @JAForbes, is funny to me, as it's one of the ones I use most frequently. I simply find that in a pipeline, prop('foo') is much more clear than obj => obj.foo. While construct/N I never use -- even though I was quite possibly the one who suggested it originally.

I'm much more interested in some of the bigger thoughts for the library, including whether we want to work exclusively with unary functions, how we want to delegate, whether we can unify all argument handling, etc. For 2.0, I really would like to see a more disciplined notion of what the library is for and how it's constructed. Questions about what functions to include should flow from these decisions, rather than on an ad hoc basis.

@semmel

This comment has been minimized.

Copy link

semmel commented Jan 7, 2019

What about the often touted "easy-to-reason-about" + readability of code advantage of FP?
What is easier to grasp?
R.multiply(10) vs. x => x * 10 ; (* not even being a proper maths symbol like 🞄) ?
R.not vs. a => !a?

IMO all these symbols && || % ! which were invented in the seventies out of the shortcomings of the ASCII character set, do not benefit readability and break easy when whitespaces are not carefully set. (% and ** being particular ridiculous.)

When being not very attentive, arrow functions could also introduce bugs when one returns an object literal:
objOf('myKey') vs. x => ({ myKey: x }) or is it not x => { myKey: x } ?

On the other hand, where the naming of the Ramda function is poor (e.g. lt, gt, subtract, divide, pluck), arrow functions are a very good replacement.

Overall I agree with @CrossEye that

Questions about what functions to include should flow from these decisions (roadmap document), rather than on an ad hoc basis.

This will save a lot of repetition in discussing the same library design topics every time again and again.

@JAForbes

This comment has been minimized.

Copy link
Contributor

JAForbes commented Jan 9, 2019

IMO all these symbols && || % ! which were invented in the seventies out of the shortcomings of the ASCII character set, do not benefit readability and break easy when whitespaces are not carefully set.

I personally dislike syntax and like function names, but I don't think it's worth the trade off for ramda specifically. I think this is similar argument to what I covered in a previous comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment