Skip to content

change propEq/pathEq parameters order#2938

Merged
CrossEye merged 1 commit into
ramda:masterfrom
adispring:change-propEq-parameters-order
Apr 21, 2022
Merged

change propEq/pathEq parameters order#2938
CrossEye merged 1 commit into
ramda:masterfrom
adispring:change-propEq-parameters-order

Conversation

@adispring

@adispring adispring commented Dec 20, 2019

Copy link
Copy Markdown
Member

Make propEq/pathEq parameters order be consistent to propSatisfies/pathSatisfies parameters order.

related to #2937

@adispring adispring changed the title [feature] change propEq/pathEq parameters order change propEq/pathEq parameters order Dec 20, 2019
@adispring adispring force-pushed the change-propEq-parameters-order branch from 540cbe6 to b9e04c8 Compare December 20, 2019 02:44
@CrossEye

Copy link
Copy Markdown
Member

Thank you very much. I think this is a very positive change, but I'd like to leave it open for a bit to hear back from others. Any objections, @ramda/core?

@Bradcomp

Copy link
Copy Markdown
Member

This is a big breaking change. I think it's good but I think it's not fair to make this without bumping to 1.0.

@Bradcomp

Bradcomp commented Apr 14, 2020

Copy link
Copy Markdown
Member

@CrossEye What do you think about using this one as an impetus to get to 1.0?

It's a positive change, and we can look at the other path stuff, clean it up, then clear this out and 💥

@CrossEye

Copy link
Copy Markdown
Member

@Bradcomp: That sounds like a very good idea.

@lambert-velir

Copy link
Copy Markdown

Any news on this? I just spent 15 minutes debugging why this didn't work:

R.propSatisfies("name", R.test(queryRegex))

@CrossEye

Copy link
Copy Markdown
Member

This is one of the few open issues that I think might be worth a breaking change in v1.0 -- or possibly a short-lived v0.29.

@customcommander

Copy link
Copy Markdown
Member

This is one of the few open issues that I think might be worth a breaking change in v1.0 -- or possibly a short-lived v0.29.

My personal preference would be to stop releasing breaking changes in minor v0 releases especially if we're committed to release v1.0 as soon as possible. As much as I prefer having a non-breaking v1.0, I think these changes bring some consistency and v1.0 is probably the best time to introduce such breaking change.

I'm in favour of this change but would prefer to release it as part of v1.0

@customcommander customcommander added this to the v1.0 milestone Apr 18, 2022
@customcommander customcommander force-pushed the change-propEq-parameters-order branch 2 times, most recently from 4affc53 to 5c1855f Compare April 18, 2022 06:25
@customcommander customcommander force-pushed the change-propEq-parameters-order branch from 5c1855f to 3b76eb1 Compare April 18, 2022 06:36
@customcommander

Copy link
Copy Markdown
Member

@adispring I took the liberty to update your branch:

  1. Rebased against origin/master to fix a conflict
  2. Fixed a failing test (somehow one test for propEq wasn't updated to reflect the change of parameter position)

@CrossEye I think this change was welcomed by pretty much everybody in the team. Could we merge this now and ship it in v1.0? I already added that milestone :)

@adispring

Copy link
Copy Markdown
Member Author

Is it more natural to keep current propEq/pathEq's parameter order? And instead, we can change propSatisfies and pathSatisfies's parameter order.

@customcommander

Copy link
Copy Markdown
Member

Is it more natural to keep current propEq/pathEq's parameter order? And instead, we can change propSatisfies and pathSatisfies's parameter order.

I think your change makes more sense as it focus on the value first as opposed to where the value is:

var isJohn = propEq('john');

isJohn('name', {name: 'john'});
isJohn('first_name', {first_name: 'john'});

With the current parameters order you would have to flip the function first.

@adispring

Copy link
Copy Markdown
Member Author

I think your change makes more sense as it focus on the value first as opposed to where the value is:

OK.

@semmel

semmel commented Apr 18, 2022

Copy link
Copy Markdown
Contributor

@customcommander wrote

I think your change makes more sense as it focus on the value first as opposed to where the value is...

How to order the arguments is not so clear cut as your comment suggests. Is not

put the least changing arguments first

the principle of Ramda?

With that principle, the "focus" should sharpen with the position of the argument – so the most general would be first.

In the test example propEq('brown', 'hair'); one could also argue that the subject "hair" is more general than the particular color "brown".

What the least-changing or most general is, may depend on the particular context. E.g. I use more often the flipped version of contains to determine that a value is contained in a known set (enumeration). In that case the enumeration is clearly the least-changing argument and should come first. However, it would be perhaps unpractical for Ramda to introduce it's functions in all permutations. For that there is flip and __.

If this change comes through, we may eliminate the inconsistency with propSatisfies, but how many other will be introduced?
E.g. assoc, adjust and update are authored in the way that least-changing is where the value is.

@customcommander

Copy link
Copy Markdown
Member

I don't think this is necessarily going against the philosophy of putting data last. We simply have different opinions about what the least-changing parameters are.

For me propEq(42) is the same as propSatisfies(equals(42)) as I consider 42 as a form of predicate. So in my view the parameters order should be consistent.

You make reasonable observations (as always) but we don't need to put everything into the same bag either. I don't see propEq and the like as related to functions such as assoc (just my opinion here).

This PR has been opened for more than two years now so we ought to make a decision at some point. Do you feel strongly against this change?

@semmel

semmel commented Apr 19, 2022

Copy link
Copy Markdown
Contributor

This PR has been opened for more than two years now so we ought to make a decision at some point. Do you feel strongly against this change?

No, I have anticipated (feared) this breaking change might come eventually and moved away from propEq. The trivial task it solves is not worth introducing potential bugs. Instead I write ({key}) => key === value.

I was just asking if someone else had a consistent conception about the order of parameters in Object-accepting functions of Ramda. This PR might be the right place to ask I thought.

@CrossEye

Copy link
Copy Markdown
Member

I was just asking if someone else had a consistent conception about the order of parameters in Object-accepting functions of Ramda. This PR might be the right place to ask I thought.

I don't think there is any universal answer to these questions. For the majority of Ramda functions the order feels immediate, and obviously correct. But some, like includes and propEq seem to have equal weight, and no wonderful answer. Here's where I really miss Haskell's left- and right-sections.

@CrossEye CrossEye merged commit a4998cf into ramda:master Apr 21, 2022
@whitelizard

Copy link
Copy Markdown

Every function should stand on its own and not be dependent on or ruled by other functions and their most logical & convenient names and signatures (arg order). I don't think that arg order is always subjective, but the name should reflect the order, that is most important.
You could probably find many different groupings among all ramda functions, but if a grouping is based on names, and we adjust signatures based on the group of names, then we are working backwards! Signature comes first, then a name that correctly reflects what the function does AND (almost more importantly) the argument order -- regardless of other functions in the library.
(See my previous post)

@Harris-Miller

Copy link
Copy Markdown
Contributor

regardless of other functions in the library.

Good APIs have consistent and predictable behavior. And that includes function signatures and argument order. My point is that they should be consistent. I'm not particular about which of the 2 orders here is correct, subject -> prop/path -> object -> boolean or prop/Path -> subject -> object -> boolean.

I don't think that arg order is always subjective

What I mean to say is the reasoning behind why the arguments should be in the order they are is subjective. Using your own example here

Example 1: A function that checks a predicate for an object prop
Step 1:
(a → Boolean) → String → {String: a} → Boolean
Note, as mentioned above, a function is to be considered highly general and "far away" from being "data". Therefor the predicate is best put first.

and

Example 2: A function that checks an object prop aganst a value
Step 1:
String → a → Object → Boolean
a is a value closer to being "data" than the name of the prop, therefor it should come later in the argument list.

Let's rewrite these 2 signatures generically.

  • Example 1: X -> Key -> Object -> Boolean
  • Example 2: Key -> X -> Object -> Boolean

The idea that a "function" is further away from "data" than "value", so Key should come second in one case and second in the other is subjective. I could argue that the Key is closer to the Object in both cases.

Besides, propEq = (value, key, obj) => propSatisfies(x => x === value, key, obj)

Whatever the order is, just have it be consistent. propEq was inconsistent before, and now it is.

I don't disagree with you that propEq should be String -> a -> Object -> Boolean, all I'm saying is that if we choose to do that, we should for propSatisfies, propIs, etc, as well.

@Harris-Miller

Copy link
Copy Markdown
Contributor

For any future argument changes, I think the community would benefit greatly from codemods that ramda could supply as part of it's release process. Similar to what react supplies: https://github.com/reactjs/react-codemod

@whitelizard

Copy link
Copy Markdown

@Harris-Miller I feel that you don't get my point.

The naming is wrong, not the signature.

As I wrote earlier: e.g. "valueAtProp" would be the better name if the prop name (key) comes first in the argument list (Key -> X -> Object -> Boolean).

It is not the start of the function name that refers to the first argument, it is the last part of the function name that should refer to the first function argument. This is because of currying.

const ageIs = R.valueAtProp('age');
const isPerson20yearsOld = ageIs(20);
const celebratePerson = isPerson20yearsOld(person);

Do you agree that the intention of ramda functions is to be able to use currying in the way like above? And that the names should read well with the argument that follows, like above?

If the rules that I wrote in my first long post is followed, then the API would be entirely consistent.

@Harris-Miller

Copy link
Copy Markdown
Contributor

Do you agree that the intention of ramda functions is to be able to use currying in the way like above? And that the names should read well with the argument that follows, like above?

@whitelizard Yes I do agree. My arguments for why propEq should have been changed as described in this MR from the start is solely for that and that alone. That does not mean that I disagree with you on the fact that the names should be different to better represent their signatures, or that the signatures should be different to better match the current names.

My goal is for consistency in behavior and expectation in the API.

If the rules that I wrote in my first long post are followed, then the API would be entirely consistent.

Also agree, as it aligns with my stated goal

@whitelizard

Copy link
Copy Markdown

So, the solution could be simple.

propEq is not really a useful function, as @crshmk has pointed out multiple times above. It could stay in the library, with arg order changed as by this MR, but the library should have the more useful function valAtProp (or whatever would be the best name). Also change the other inconsistent functions (their names!) according to @semmel.

Release 1.0.0.

Again, the order of designing any API function should be:

Need -> Signature -> Name

Never change the signature of a function to match the faulty name it unfortunately got. The signature was designed out of greatest utility. The name should change if it got a bad one. Changing the name is not initially a breaking change, it could start with a minor release of adding the new name to the API and flagging the old as "will be removed in the next major". Ramda could have this as part of its change strategy.

@Gastonite

Gastonite commented Aug 17, 2023

Copy link
Copy Markdown

100% agree with @whitelizard
And I would add that I think it's preferable to consider the users of the library and their (often large) code base when planning such a huge change. By this I mean the difficulty of the migration needed when inverting the parameters order of a curried function, which could be used in several ways, compared to the simplicity of the migration if we just changed its name.

I also agree with having consistent functions in a library is a good thing, but maybe it would be a good thing also to add another function (temporary or not) to replace with when migrating.

@Harris-Miller

Harris-Miller commented Aug 29, 2023

Copy link
Copy Markdown
Contributor

@whitelizard just to be clear, to extend on this table

existing fn signature consistent name
allPass [(*… → Boolean)] → *… → Boolean passesAll
anyPass [(*… → Boolean)] → *… → Boolean passesAny
append a -> [a] -> [a] appendValue
appendFlipped [a] -> a -> [a] appendTo
nthArg Number → *… → * argNth
  [Idx] → a → {a} → Boolean eqPath
  Idx → a → {a} → Boolean eqProp
included [a] → a → Boolean includedIn
pathEq a → [Idx] → {a} → Boolean valAtPath
propEq a → Idx → {a} → Boolean valAtProp

While obviously incomplete, the table just represents the idea of deprecating existing fn columns, adding their replacements in the consistent name column, as well as adding new functions that don't currently exist

For the sake of typescript I personally like having complementary functions eqProp/valAtProp, append/appendValue, etc. There is currently no good way to type flip for functions of arity 3+. Having both varieties of all these functions would help a lot in that regard.

CC: @kedashoe

@semmel

semmel commented Aug 29, 2023

Copy link
Copy Markdown
Contributor

For the sake of typescript I personally like having complementary functions eqProp/valAtProp, append/appendValue, etc. There is currently no good way to type flip for functions of arity 3+. Having both varieties of all these functions would help a lot in that regard.

Yes, and I would extend that to a few binary functions too. They have crept up in Ramda-adjunct eg. R.includes ≡ flip(RA.included), R.append ≡ flip(RA.appendFlipped), but in Ramda too e.g. R.difference ≅ flip(R.without).

This is always when "…the reasoning behind why the arguments should be in the order they are is subjective".

Another example which comes to my mind is concat. Written in a function pipeline pipe(() =>a, concat(b))() I always need to imagine the binary form concat(x, y) = [...x, ...y] to find out, that the pipeline kind of appends all of a to b and not the other way round. That reasoning is ok. Is is not too hard to figure out and consistent in Ramda (difference, subtract).

On the other hand, while you @Harris-Miller mention it, flip for functions of arity 3+ gives my headaches — and not because of TypeScript. I think its an anti-pattern. (Just as a side-note)

@jktravis

jktravis commented Sep 5, 2023

Copy link
Copy Markdown

I know this has been beat to death, but I'd just like to add my two cents. I really hate this change. I just propEq and pathEq way more than propSatisfies. Plus, the naming, as other has suggested, practically tells you how to use it: prop, then equals. I've always had to look up the docs when needing propSatisfies simply because of the naming inconsistency. But even still, I think it would have made more sense to change propSatisfies to match the other two. /shrug

@papaver

papaver commented Nov 6, 2023

Copy link
Copy Markdown

would it be possible to mention this change in the propEq documentation at least? it's extremely confusing for anyone using 0.28.0 that hasn't read this thread. i just wasted several hours trying to figure out why my code was working in the doc page but not in my code.

thanks.

@michaek

michaek commented May 6, 2024

Copy link
Copy Markdown

Ouch. It seems pretty onerous have to change any and all calls to propEq/pathEq for such a flimsy and subjective reason. I get that the library will probably not change back to its historical API at this point, but this seems hostile to longtime users, and it certainly makes me consider the wisdom of moving away from a dependency on Ramda.

@whitelizard

whitelizard commented Oct 16, 2024

Copy link
Copy Markdown

@whitelizard just to be clear, to extend on this table

existing fn signature consistent name
allPass [(*… → Boolean)] → … → Boolean passesAll
anyPass [(
… → Boolean)] → *… → Boolean passesAny
append a -> [a] -> [a] appendValue
appendFlipped [a] -> a -> [a] appendTo
nthArg Number → *… → * argNth
  [Idx] → a → {a} → Boolean eqPath
  Idx → a → {a} → Boolean eqProp
included [a] → a → Boolean includedIn
pathEq a → [Idx] → {a} → Boolean valAtPath
propEq a → Idx → {a} → Boolean valAtProp
While obviously incomplete, the table just represents the idea of deprecating existing fn columns, adding their replacements in the consistent name column, as well as adding new functions that don't currently exist

For the sake of typescript I personally like having complementary functions eqProp/valAtProp, append/appendValue, etc. There is currently no good way to type flip for functions of arity 3+. Having both varieties of all these functions would help a lot in that regard.

CC: @kedashoe

I believe all number comparison functions are also the wrong order: lt, gt, lte, gte.

Again, partial application and readability:

const below20 = R.lt(20); // is read "less than 20"
const isYoung = below20(age);

@kedashoe

Copy link
Copy Markdown
Contributor

I believe all number comparison functions are also the wrong order

#1497

@michaek

michaek commented Oct 16, 2024

Copy link
Copy Markdown

It may be fine to change the signature of the functions so they "read correctly", but please make a 1.0 release with the current signature, and then make breaking changes in a future major version. Stability and reliability are very important for a library like ramda.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.