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

make gt, gte, lt, lte, etc... right associative #1497

Open
luizbills opened this issue Nov 4, 2015 · 119 comments
Open

make gt, gte, lt, lte, etc... right associative #1497

luizbills opened this issue Nov 4, 2015 · 119 comments

Comments

@luizbills
Copy link

luizbills commented Nov 4, 2015

actual:

var gtFive = R.gt(5);
gtFive(6); // => false

proposal:

var gtFive = R.gt(5);
gtFive(6); // => true
@davidchambers
Copy link
Member

👍

@buzzdecafe
Copy link
Member

been down this road before. Under this proposal gte(5)(6) !== gte(5, 6). that is surprising. this is where the placeholders came from, the desire to right-section the non-commutative infix operators.

@davidchambers
Copy link
Member

I'm not suggesting shenanigans. I'm in favour of swapping the order of the arguments. One can easily write x < y if both values are known, so these functions are primarily useful in conjunction with partial application. Let's optimize for partial application (as we do elsewhere in the library).

@buzzdecafe
Copy link
Member

I'm in favour of swapping the order of the arguments.

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 concat?

Feels very familiar.

@davidchambers
Copy link
Member

divide and subtract, absolutely!

concat I'm not sure about. I do like the fact that R.reduce(R.concat, '', ['foo', 'bar', 'baz']) evaluates to 'foobarbaz'.

@CrossEye
Copy link
Member

CrossEye commented Nov 5, 2015

I do like the fact that R.reduce(R.concat, '', ['foo', 'bar', 'baz']) evaluates to 'foobarbaz'.

That's my take on the whole concept. Although I'm very frustrated by gt(5) not feeling correct, enough so that I've gone searching for solutions that lead us down inappropriate paths in the past., equivalences like these seem pretty important. It's not just concat.1

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 (merge, range, zip, zipWith), with the non-commutative ones highlighted.

  • add
  • both
  • concat
  • difference
  • differenceWith
  • divide
  • either
  • equals
  • gt
  • gte
  • identical
  • intersection
  • intersectionWith
  • lt
  • lte
  • mathMod
  • max
  • maxBy
  • merge ?
  • min
  • minBy
  • modulo
  • multiply
  • range ?
  • subtract
  • union
  • unionWith
  • zip ?
  • zipWith ?

If we were to change subtract, should we not also change difference? And yet all prior art I can find for this in other libraries or languages, all have the same API order as Ramda currently supplies for this function.

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

One way to understand (foldr f a) is as a function that replaces all occurrences of Cons in a list by f, and all occurrences of Nil by a. Taking the list [1, 2, 3] as an example, since this means

Cons 1 (Cons 2 (Cons 3 Nil))

then (foldr (+) 0) converts it into

(+) 1 ((+) 2 ((+) 3 0)) = 6

With infix operators, this is nicer still, essentially replacing commas with operators.

@asaf-romano
Copy link
Member

How about providing flipped versions, either suffixed (gt_, lte_, etc), or as a properties (gt._, ...)?

@buzzdecafe
Copy link
Member

How about providing flipped versions

we had that for a while. dropped in favor of placeholder e,g. var greaterThan5 = R.gt(R.__, 5)

I'd be happy to see those come back and placeholder go away.

@CrossEye
Copy link
Member

CrossEye commented Nov 5, 2015

How about providing flipped versions, either suffixed (gt_, lte_, etc), or as a properties (gt._, ...)?

People have really not liked the properties version in the past, but I like the suffix. At one point we had divideBy and subtractN, but there was never a good answer to how to extend this to things like gt. The suffixed version would do this unambiguously.

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 partialN function to compensate for the lost functionality -- just a function to partially apply argument n. That's a minor detail, though.

I really like this idea!

@paldepind
Copy link
Member

I'd be happy to see those come back and placeholder go away.

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 R.gt(5) or R.zipWith(R.gt, [1,2,3], [3,2,1]).

I think suffixed versions would actually be a nice compromise. Having a suffix _ meaning "slightly different variant" could be useful in other cases as well.

@raine
Copy link
Member

raine commented Nov 5, 2015

Why do you consider placeholders a bad thing?

I'm almost certain I would miss them were they abolished.

@paldepind
Copy link
Member

@raine They certainly can be very nice. There was some related discussion in #1363.

@scott-christopher
Copy link
Member

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

@CrossEye
Copy link
Member

CrossEye commented Nov 5, 2015

@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 gt also works for subtract.

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.

@scott-christopher
Copy link
Member

@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.

@TheLudd
Copy link
Contributor

TheLudd commented Nov 6, 2015

I have been thinking the same way as @scott-christopher. I would prefer functions isGt and subtractBy. These names are IMO much better than gt_*

* The suffix _ is a ramda standard that means that the function is slightly different than the original one. It probably means that the function is flipped but could really be anything and you'll need to figure out exactly what by reading the docs.

@buzzdecafe
Copy link
Member

It probably means that the function is flipped but could really be anything and you'll need to figure out exactly what by reading the docs.

😆

@paldepind
Copy link
Member

The advantage of the suffix is that the same solution that works for gt also works for subtract.

Are we really interested in having flipped gt? gt_ would be equal to lt and lt_ would be equal to gt. I.e. they would just be aliases.

@buzzdecafe
Copy link
Member

Are we really interested in having flipped gt? gt_ would be equal to lt[e] and lt_ would be equal to gt[e]. I.e. they would just be aliases.

that's true. the only thing motivating this discussion is that gt(5) does not mean "greater than five", for that you have to either flip(gt)(5) or gt(__, 5).

@CrossEye
Copy link
Member

CrossEye commented Nov 6, 2015

Are we really interested in having flipped gt? gt_ would be equal to lt and lt_ would be equal to gt. I.e. they would just be aliases.

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.

@paldepind
Copy link
Member

Yes. I do understand how R.gt and R.lt are very unnatural when partially applied.

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.

@CrossEye
Copy link
Member

CrossEye commented Nov 6, 2015

I don't have a serious objection to isGt, isLt, etc. My preference is for the underscore suffix as we could then be entirely consistent, and use it for all non-commutative operator functions. It is slightly less discoverable than isXX, but is not bad, and it certainly looks cleaner to me than lt(__, 5) or flip(lt)(5).

I'll just be glad to do something to clean this up!

@scott-christopher
Copy link
Member

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:

* isGt
* isGte
* isLt
* isLte
* divideBy
* subtractBy
* mathModBy
* moduloBy

I was tempted to add concat to the list, however unlike those functions it does feel somewhat appropriate to just apply flip to it.

@CrossEye
Copy link
Member

CrossEye commented Nov 7, 2015

@scott-christopher:

This list feels reasonably correct. (I might throw in difference as well.) But subtractBy doesn't feel particularly appropriate. There's another issue with *By, going back to Issue #65, where we decided to use With and By prefixes in a standard manner. somethingBy would accept a unary function that generated a representative value of each element in the list, such as perhaps a sort key. somethingWith would accept a binary (possibly other polyadic?) function that would accept two elements of the list and use the result of that function in some way. This has been stretched a bit to cover things like zipWith, where the elements are not actually both from the same list. And we have an unfortunate holdover from before we created this convention in useWith. But for the most part, that's how we've been using these suffixes.

My inability to find a good name for a flipped subtract, as well as my own personal inability to come up with isGt, isLte, etc., is one of the reasons that I found subtract_, lt_, etc. attractive. So, while divideBy sounds just right, and the modulo names are all right with By, it would be a shame to abandon our current convention, especially as subractBy really doesn't capture it at all. (The previous incarnation was not great, either: subtractN.)

@scott-christopher
Copy link
Member

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 ... ¯_(ツ)_/¯

@asaf-romano
Copy link
Member

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.

@asaf-romano
Copy link
Member

Btw, objOf is missing in these lists.

@paldepind
Copy link
Member

Using a postfix _ certainly has it's benefits. It is easy to apply to all relevant names. There is a nice consistency in it. Users would only have to learn about the convention once and would then understand it's meaning in all circumstances we choose to use it in.

One point though: if _ means "flipped" then why not use "F" instead for its mnemonic value?

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 _ postfixed flipped version is not going to solve that! People will still have to know that R.gt(5) is misleading and not what they want even though it seems right.

I think R.isGt comes closer to solving that underlying problem because R.isGt(5) reads better than R.gt(5) and thus guides users toward the right choice.

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.subtract to R.subtractFrom and then make R.subtract a flipped R.subtractFrom. Again, these names would guide users towards the right choice:

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.

@CrossEye
Copy link
Member

CrossEye commented Oct 5, 2018

@1024gs:

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.

@foxbunny
Copy link
Contributor

foxbunny commented Oct 5, 2018

@CrossEye

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 flip() in cases like reduce(concat).

@foxbunny
Copy link
Contributor

foxbunny commented Oct 5, 2018

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.

@1024gs
Copy link

1024gs commented Oct 5, 2018

@CrossEye
I did not mean to automate it (sometimes automation is the biggest source of bugs).
I meant to make gt, gte, lt, lte, etc... right associative and in cases like reduce(concat) I would suggest, and I think it is a good suggestion, the user to use flip().
e.g. reduce(flip(concat), '', ['a', 'b', 'c']) would evaluate to 'abc'

@davidchambers
Copy link
Member

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. :)

@josuamanuel
Copy link

josuamanuel commented Jan 7, 2021

I saw a comment indicating that this proposal:

would replace

R.reduce(R.subtract, 12, [1, 2, 3, 4])

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.

const mySubtract = R.curryN(2, (a, b) => b-a)
const myReduce = R.curryN(3, reduce)

myReduce(mySubtract, 12, [1, 2, 3, 4])    // (12 - 1) -2) -3) -4 = 2

function reduce(reducer, ini, array)
{
  let acum = ini
  for (i of array) {
    acum = reducer(i, acum)
  }
  return acum
}

The signature for the reduce:
((a, b) → b) → b → [a] → b

const myReduce = (reducer, ini, array) => {

  let acum = ini
  for (i of array) {
    acum = reducer(i, acum)
  }
  return acum
}

To me append works (data last parameter):
R.append(2, [1]) // [1,2]

Then to be conssistent:
R.concat('s', 'cat') // should return cats... but returns scat.

Changing the order of the params in the reducer and in the concat we see that, it keeps being intuitive.

myConcat = R.curryN(2, (a,b) => b + a)

myReduce(myConcat, '', ['foo', 'bar', 'baz']) //? foobarbaz

@CrossEye
Copy link
Member

CrossEye commented Jan 9, 2021

@josuamanuel:

I think changing the order of arguments in the reduction function passed to reduce now is a non-starter. They've been in this order since the first version of Ramda. They match the order used in Array.prototype. They match the FantasyLand specification. They match Haskell's foldl. They match nearly every library that supports a fold.

Ramda seems stuck with a problem with curried versus fully applied non-commutative binary operations. I wish we had Haskell's sections available.

@josuamanuel
Copy link

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,[3])
The system or function wants to add a value two acting upon the data [3]

R.append(2)
This is an appender of 2... A worker prepared to add a 2 in any data array.

// This is the flip vevrsion of Ramda.
gt(5,6)
The system/function checks if it is great than 5 giving 6 as data

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:
Subject: Omitted in the language as it is understood by the context. The function or the system that wants to do the action.
Verb: function name:
Direct Object. Parameters
Indirect Object or Complements: data (last parameter)

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.

@sebastienfilion
Copy link
Contributor

@josuamanuel
The syntax of Ramda is influenced by Lambda Calculus which is often time read from right to left; when you think of everything being a function, it makes a lot of sense.
When you use Ramda within a tacit code-base you also realize that it couldn't be any other way. Tacit programming (or point-free) is a style of programming where no argument are declared (or no points). And so when you think of: if x is greater than 10, well, you know the value 10 but not x; so passing x first doesn't make sense.
Take the following example: if x is less than or equal to 10, then add 1 else return 0;

const fuga = when(lte(10), add(1), always(0));

In summary, Ramda's API always pass the undetermined value last.

If you want gte or lte to read in the other direction, you can always do this: const my_gte = flip(gte);

@josuamanuel
Copy link

fuga = when(lte(10), add(1), always(0));

The example you mention doesn't work to me. I think you meant:

let fuga = R.ifElse(R.lte(10), R.add(1), R.always(0));

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

@sebastienfilion
Copy link
Contributor

sebastienfilion commented Jan 10, 2021

The example you mention doesn't work to me. I think you meant:
let fuga = R.ifElse(R.lte(10), R.add(1), R.always(0));

Yes, you're right about that, I meant to use the ifElse combinator -- I probably shouldn't write tech comments 10 minutes after waking up. 🙃

But your argument doesn't take into account the origin of the various combinators implemented by Ramda.
As it was mentioned before, concat is implement based on the Semigroup algebra defined by Fantasy-Land where the first value will be concatenated with a second value and return the first; it's not obvious when you think of concat in term of strings or arrays, but if you allow me to use an example from one of my libraries, it might help make sense.

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 concat and append are consistent.

@josuamanuel
Copy link

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.

@CrossEye
Copy link
Member

@josuamanuel:

The fact we use mathematical terminology like non-commutative binary operation to describe functions sounds suspicious to me.

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.

We need to embrace the descriptive nature of functions as grammar, syntax and semantic.

Our real trouble here is that there are conflicting expectations. We want subtract (7, 3) to yield 4. It feels most natural. But we also want subtract (3) to be a function that subtracts three from the argument supplied, so that subtract (3) (7) yields 4. The way Ramda is built, we can't have it both ways. If I were to start over, I would insist in focusing on the unary application, and make the binary version follow that, so that subtract (3, 7) would read the way you're suggesting, something like "subtract 3 from 7". The trouble is that there seem to be no good answers. And ones like concat or the reduce callback are further constrained by prior art in a way that Ramda would not want to disturb.

I'm afraid I simply don't see good answers here.

@davidchambers
Copy link
Member

Why not drop support for subtract (7, 3) and require subtract (3) (7)? We made this change to Sanctuary several years ago, throughout the library. Not only did this dramatically simplify parts of the implementation, but it made the library easier to understand and use. With Ramda, one must remember that although R.reduce is curried, the function one provides as its first argument must not be. As a Ramda user, this distinction seems arbitrary.

The currying section of Sanctuary's website compares the two approaches.

@semmel
Copy link
Contributor

semmel commented Jan 11, 2021

@davidchambers

Why not drop support for subtract (7, 3) and require subtract (3) (7)? … Not only did this dramatically simplify parts of the implementation, but it made the library easier to understand and use.

I guess replacing each , with )( that would make ramda functions look a bit alien in the code base. Also it would make it even harder for the eye to associate the parentheses to the according group.
Here the ifElse example from the docu

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.
When this would be required also for pipe, compose and o it would become even worse looking.

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.

R.reduce is curried, the function one provides as its first argument must not be. As a Ramda user, this distinction seems arbitrary.

There are more Ramda functions where this is the case e.g. converge and useWith. (Of course ramda-style curried functions always work).

Here is how some of ramda looks for binary/ramda-curried/strictly-curried functions.

strictly add = a => b => a + b ramda-style R.add binary add = (a, b) => a + b example
chain - R.chain(add, R.inc)(7) // 15
ap - R.ap(add, R.inc)(7) // 15
useWith - R.useWith(R.add, [R.inc,R.multiply(8)])(3,4) // 36
converge - R.converge(add, [R.inc,R.multiply(8)])(3) // 28
lift R.lift(add)(R.inc,R.multiply(8))(3) // 28
reduce - R.reduce(R.add, 0, [7, 8]) // 15

Perhaps the gaps in the first column could be filled in the implementations?

@davidchambers
Copy link
Member

@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))));

Perhaps the gaps in the first column could be filled in the implementations?

Good idea!

@CrossEye
Copy link
Member

CrossEye commented Jan 12, 2021

@davidchambers:

Why not drop support for subtract (7, 3) and require subtract (3) (7)?

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 reduce and maybe one or two other places might still end up being exceptions to maintain compatibility with FantasyLand. Either that or there's some other delegation strategy for FantasyLand. But that latter seems doable now.

@davidchambers
Copy link
Member

I know that the callback to reduce and maybe one or two other places might still end up being exceptions to maintain compatibility with FantasyLand.

No such exception is necessary. S.reduce takes a curried binary function but provides an uncurried binary function when invoking fantasy-land/reduce. R.reduce could do the same.

@sebastienfilion
Copy link
Contributor

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))));

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.
I'll have to experiment with this syntax on a personal project.

@CrossEye
Copy link
Member

CrossEye commented Jan 12, 2021

@davidchambers:

I know that the callback to reduce and maybe one or two other places might still end up being exceptions to maintain compatibility with FantasyLand.

No such exception is necessary. S.reduce takes a curried binary function but provides an uncurried binary function when invoking fantasy-land/reduce. R.reduce could do the same.

Right, that's what I meant by the next sentence:

Either that or there's some other delegation strategy for FantasyLand.

There is still an issue for other delegation. Which format would Ramda apply to an arbitrary object that has a reduce function? I know that this is not an issue that Sanctuary has to worry about. But Ramda has always been more ... let's say promiscuous ... about its delegation.

@pipedreambomb
Copy link

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.

@davidchambers
Copy link
Member

@pipedreambomb, if you're willing to add another dependency you could use S.gt:

> S.filter (S.gt (1)) ([0, 1, 2, 3, 4])
[2, 3, 4]

@pipedreambomb
Copy link

@pipedreambomb, if you're willing to add another dependency you could use S.gt:

> 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.

@semmel
Copy link
Contributor

semmel commented Jun 19, 2021

I'm going ahead with the placeholder idea (gt(__, 1), but I still think it looks ugly.

Indeed. I just drop point-free style in favour of x => x > 1. I guess, had there been ES6 arrow functions, gt, lt, and their likes would have never made it into Ramda.

@davidchambers
Copy link
Member

I guess, had there been ES6 arrow functions, gt, lt, and their likes would have never made it into Ramda.

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)]

@pipedreambomb
Copy link

I'm going ahead with the placeholder idea (gt(__, 1), but I still think it looks ugly.

Indeed. I just drop point-free style in favour of x => x > 1. I guess, had there been ES6 arrow functions, gt, lt, and their likes would have never made it into Ramda.

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

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

No branches or pull requests