-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
make gt, gte, lt, lte, etc... right associative #1497
Comments
👍 |
been down this road before. Under this proposal |
I'm not suggesting shenanigans. I'm in favour of swapping the order of the arguments. One can easily write |
Does this argument order hold for any non-commutative infix operator converted to prefix? gt(5, 10) === true // ?
divide(20, 2) === 0.1 // ?
subtract(10, 9) === -1 // ? How about Feels very familiar. |
|
That's my take on the whole concept. Although I'm very frustrated by R.reduce(R.subtract, 12, [1, 2, 3, 4]) should be equivalent to 12 - 1 - 2 - 3 - 4 This proposal would replace that with 4 - (3 - ( 2 - (1 - 12))) which is neither as intuitive nor as understandable. My biggest difficulty is with the fact that I would want to remain consistent across all our operators. Here, I think are all the current functions that can reasonably be called operators, including a few more questionable ones (
If we were to change I really would like to solve the underlying problem. I simply don't see how to do that. So I'm afraid I'm 👎 on this. 1I think I first saw this in John Hugh's seminal paper, Why FP Matters, where he points out that
With infix operators, this is nicer still, essentially replacing commas with operators. |
How about providing flipped versions, either suffixed (gt_, lte_, etc), or as a properties (gt._, ...)? |
we had that for a while. dropped in favor of placeholder e,g. I'd be happy to see those come back and placeholder go away. |
People have really not liked the properties version in the past, but I like the suffix. At one point we had If we go this route, the big win, as @buzzdecafe points out, would be allowing us to drop the placeholder. But I think to do that we might want to add a I really like this idea! |
Getting rid of placeholders would certainly be nice. No matter how the parameters are arranged one use case is going to be awkward. It will either be I think suffixed versions would actually be a nice compromise. Having a suffix |
Why do you consider placeholders a bad thing? I'm almost certain I would miss them were they abolished. |
To throw another suggestion into the mix, perhaps we could offer them as unary functions that return predicates instead, e.g.: isGt :: Ord a => a -> (a -> Boolean)
isLt :: Ord a => a -> (a -> Boolean)
// etc.
isGt(5) :: Number -> Boolean |
@scott-christopher: We could, but I think that would become more ad hoc. The advantage of the suffix is that the same solution that works for And with the underscore as the suffix it retains something of the feel of placeholders while making me think just a bit about right sections. |
@CrossEye You're right, that would only make sense for functions returning boolean values. I could get behind the underscore suffix as somewhat of an analog to a prime. |
I have been thinking the same way as @scott-christopher. I would prefer functions * The suffix |
😆 |
Are we really interested in having flipped |
that's true. the only thing motivating this discussion is that |
In the United States, voting age is 18. People who haven't reached their 18th birthday are not allowed to vote; those who have, are. I would much rather express this as gte_(18) than as lte(18) which does not even come close to expressing my intent. That's the reason I would like these. |
Yes. I do understand how But Ramda currently has a no alias policy. Thus it seems worth noting that we'd essentially be creating aliases. Another idea: R.isGt = R.lt
R.isLt = R.gt It reads like plain english when partially applied: R.isGt(17); Edit: I just saw that @scott-christopher suggested the exact same names above. |
I don't have a serious objection to I'll just be glad to do something to clean this up! |
Of the list added by @CrossEye above, if we were to go down the path of additional functions I'd propose only the following be included for now:
I was tempted to add |
This list feels reasonably correct. (I might throw in My inability to find a good name for a flipped |
I don't mind the underscore suffix if it means resolving this. My only slight concern is that it sets a bit of a precedence for the inclusion of functions with very similar functionality to those that already exist by simply adding an underscore to the end, where previously these would have been suggested as additions to the cookbook. I'm not suggesting that we'll actually start approving them, but it may result in more PRs of that nature. Perhaps that's not such a problem ... ¯_(ツ)_/¯ |
That's one of the reasons my other suggestion was gt**.**_e. Such a solution could be partially automated, so that all binary functions in the library have that property. I can see why people would prefer a simple suffix though. |
Btw, objOf is missing in these lists. |
Using a postfix One point though: if I don't think it solves the main problem though: that in the current API the most intuitive thing to do yields the wrong result. Adding a I think I'm guessing that to someone who has never used Ramda before this: R.map(R.isGt(3), [1,2,3,4,5]); will look more sensible than this: R.map(R.gt_(3), [1,2,3,4,5]); To me an API design goal is that users can look at the function names only and reasonably guess which function is appropriate. With the above goal in mind we could rename R.map(R.subtract(4), [1,2,3,4]); // [-3,-2,-1,0]
R.map(R.subtractFrom(4), [1,2,3,4]); // [3,2,1,0] Both of the above seems natural to me. |
I don't think that's at all possible. We don't know when that's happening. A function doesn't know how and where it's being called. And a function that accepts another function as a parameter does not know that it is a non-commutative operator. I see no way to automate this. |
I don't think they meant that. I believe they were simply suggesting flipping the argument order as a solution, and asking people to use |
To throw in my $0.02, Ramda already has so many functions and, postfix or otherwise, I think additional functions would just make things worse overall. |
@CrossEye |
Here's your suggestion in action, @1024gs: > S.map (S.sub (1)) ([1, 2, 3, 4, 5])
[0, 1, 2, 3, 4]
> S.reduce (S.flip (S.sub)) (1000) ([1, 2, 3, 4, 5])
985 I've had no problems with this behaviour. :) |
I saw a comment indicating that this proposal: would replace
With 4 - (3 - ( 2 - (1 - 12))) I see the logic, though I think I have a problem with the order of parameters in the reducer function as well . The reducer function (I mean the first parameter of the R.reduce) currently receives the accumulation and then the current value... I see the reducer as acting on the data that is being accumulated. This will be consistent with data as last parameter rule. And changing the order in the reducer would make this to works again.
The signature for the reduce:
To me append works (data last parameter): Then to be conssistent: Changing the order of the params in the reducer and in the concat we see that, it keeps being intuitive.
|
I think changing the order of arguments in the reduction function passed to Ramda seems stuck with a problem with curried versus fully applied non-commutative binary operations. I wish we had Haskell's sections available. |
I see your point to reject the change... and I kind of agree. Anyway, and just for the purpose of entertaining our minds, let me explain my view. This is coming from being new into functional programming, and this could be bad for obvious reasons and good to see things in a different way. My approach comes from a language (grammar syntax and semantic) rather than mathematical notation. We cannot create operators in javascript and we need to express them with functions. The change to functions requires an effort to make them more natural language readable. The fact we use mathematical terminology like non-commutative binary operation to describe functions sounds suspicious to me. We need to embrace the descriptive nature of functions as grammar, syntax and semantic. This why I was really impressed with the concept of data las parameter of a function. I took some time to adapt coming from lodash, but I like the consistency as a grammar rule. After some little time, I adapted quite well. For the first time I felt I could take a consistency decision about the order of parameters in new functions. The way I read Ramda code: R.append(2) // This is the flip vevrsion of Ramda. When considered gt(5,6) and gt(5)(6) both as true from data las parameter perspective it makes sense. Grammar doesn't need to be intuitive at first glance. But consistency and narrative of the language is more important. I think what it helps me to separate the parameters from the data is to see the semantic of name of the function... the verb + adding -er: What is a divider, what is an adder, a subtracter, a merger, a reducer. As being separated from the data they act upon. Grammar: Probably, making this more language oriented would have changed some verb names to fit better into this semantic. My intention with the post... is to give another view. I have say I admire the good work that all contributor makes. |
@josuamanuel const fuga = when(lte(10), add(1), always(0)); In summary, Ramda's API always pass the undetermined value last. If you want |
The example you mention doesn't work to me. I think you meant:
And it looks like you fall in the trap we are describing. The above syntax currently means: if 10 is less or equal than x, then add 1 else return 0 rather than the more intuitive (and what you thought it meant): if x is less than or equal to 10, then add 1 else return 0 |
Yes, you're right about that, I meant to use the But your argument doesn't take into account the origin of the various combinators implemented by Ramda. const file = concat(File("./hoge", new Uint8Array([ 65, 66, 67, 68, 69 ]), 3), File("./piyo", new Uint8Array([ 70, 71, 72, 73, 74 ]), 4));
assert(file.path === "./hoge");
assert(file.raw === new Uint8Array([ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74 ])); My argument was that the last argument is the undetermined one, so |
I agree with you that I am not taking the origins of Lambda Calculus and algebra defined by Fantasy-land. I favoured Data last parameter approach overall to favour pipes, partial applications and recipe code style. I see we are dropping data last parameter rule in favour of consistency with the origins for some functions. Thanks... for all explanations, I understand now the reasons so I can reconcile and accept things as they are. |
Understood. We often let terminology get out of hand. But it is convenient too. I hope "binary operation" (or "binary function") is clear: an operation / function that takes two arguments. While we could use the phrase "binary functions which might yield different results when the arguments are switched", it gets to be a mouthful. "commutative" is taken from mathematics, and there are plenty of times when I think such terminology goes overboard, I also think there are times when it's quite useful. Those who've been in the industry a while also had to learn to use such terms as "polymorphic", until by now it just seems natural.
Our real trouble here is that there are conflicting expectations. We want I'm afraid I simply don't see good answers here. |
Why not drop support for The currying section of Sanctuary's website compares the two approaches. |
I guess replacing each var incCount = ifElse(
R.has('count'),
R.over(R.lensProp('count'), R.inc),
R.assoc('count', 1)
);
var incCount = ifElse
(R.has('count'))
(R.over(R.lensProp('count'))(R.inc))
(R.assoc('count')(1)) Without syntax highlighting this would be a nightmare to edit. Also when writing lib functions I grew quite fond of ramda-style currying because it gives much freedom for the user: He/she can use the functions in classical style of point-free functional style - it is flexible with regard to the user's philosophy. It has certainly helped me first to become acquainted with ramda and now more and more embracing point-free function pipelines.
There are more Ramda functions where this is the case e.g. Here is how some of ramda looks for binary/ramda-curried/strictly-curried functions.
Perhaps the gaps in the first column could be filled in the implementations? |
@semmel, we addressed the formatting issue in sanctuary-js/sanctuary#438. To me (three years later), this is readable: var incCount = ifElse (R.has ('count'))
(R.over (R.lensProp ('count'))
(R.inc))
(R.assoc ('count')
(1)); One more example: // highlightChunk :: String -> Html
const highlightChunk =
def ('highlightChunk')
({})
([$.String, Html.Type])
(syntax ('keyword')
(/\b(const|else|function|if|instanceof|new|return|this)\b/)
(syntax ('boolean-literal')
(/\b(false|true)\b/)
(syntax ('number-literal')
(/\b([0-9]+(?:[.][0-9]+)?)\b/)
(Html.encode))));
Good idea! |
I'd like to do this, to go strictly unary everywhere.1 But I think that wanders far from the expectations of what Ramda is. Funny, before seeing your response, I was chatting with @buzzdecafe about exactly this. I've been thinking of what I'd want to do in a successor library., and this is definitely something I'd prefer to include. As to your concern @semmel, about layout, I've taken to writing most of my non-Ramda code in a fully curried format, and this looks totally normal to me: const incCount = ifElse
(has ('count'))
(over (lensProp ('count')) (inc))
(assoc ('count') (1)) But I really am thinking more about a successor to Ramda than about changes to Ramda itself. The large userbase makes it less appealing to radical changes, even at a major version. Also, I really like that table, and it would be nice to fill in some gaps. 1 I know that the callback to |
No such exception is necessary. |
It took me a hot minute to realized what was going on here -- it's very interesting approach. I already feel like I'm swimming up stream trying to build functional libraries for the general community; Ramda has the benefits of being easy to pick up. |
Right, that's what I meant by the next sentence:
There is still an issue for other delegation. Which format would Ramda apply to an arbitrary object that has a |
I just want to say that not only does it lead to some really unintuitive to read code, but I actually can't even work it out in my head to begin with. I'm trying to do something super basic (if thing is greater than 1, thing-- else other thing), and I'm in the REPL and I still can't work it out! Isn't lte the opposite of gt? Never mind... probably just a blind spot in my particular mind. I'm going ahead with the placeholder idea (gt(__, 1), but I still think it looks ugly. Was hoping there'd be an alternative in Ramda Adjunct, but I can't find one. |
@pipedreambomb, if you're willing to add another dependency you could use > S.filter (S.gt (1)) ([0, 1, 2, 3, 4])
[2, 3, 4] |
Thanks, I'll think about it. I think it might get confusing, mixing and matching similar libraries, but on the other hand, it's just a personal project, so I might be able to keep it straight. Might mess up my nice VS code auto-completes that automatically import the Ramda functions I want though, having another library with similar names for functions. |
Indeed. I just drop point-free style in favour of |
Ramda half-heartedly supports Fantasy Land, but in a library that embraces Fantasy Land these functions are far more versatile than their equivalent operators: > S.lte ([1, 2, 3]) ([1, 2])
true
> S.lte ([1, 2, 3]) ([1, 3])
false
> S.filter (S.lte (S.Just (2))) ([S.Nothing, S.Just (1), S.Just (2), S.Just (3)])
[Nothing, Just (1), Just (2)] |
I like that better, actually, good idea. I kinda get lost in the "point-free or die" mentality of a new convert. Idiomatic or idiotic, I guess you could also call it :P |
actual:
proposal:
The text was updated successfully, but these errors were encountered: