These are chat archives for ramda/ramda

3rd
Oct 2018
Jonathan Chang
@jdkschang
Oct 03 2018 00:02
Is there any reason to use compose over pipe?
As far as I can deduce it from the docs, both function relatively the same with the only difference respectively being right-to-left or left-to-right function composition
Baqer Mamouri
@bmamouri
Oct 03 2018 01:41

This looks a bit overly complicated to delete an item from a list:

var timelineDelete = (state, idx) => R.pipe(
  R.map(
    R.when(
      R.propEq('idx', idx),
      R.empty
    )
  ),
  R.filter(R.complement(R.isEmpty))
)(state)

Is there any more elegant to do the same?

Pedro Ávila
@avilapedro
Oct 03 2018 02:44

@bmamouri this is probably how I'd do it:

const mystate = [
  { idx: 87, name: 'Hello' },
  { idx: 54, name: 'World' }
]

const removeOne = curry ( (idx, list) => remove (idx) (1) (list) )

const timelineDelete = (state, idx) =>
  ap
    (flip (removeOne))
    (findIndex (propEq ('idx') (idx)))
    (state)

timelineDelete (mystate, 54)
// => [{"idx": 87, "name": "Hello"}]

REPL: https://goo.gl/KS3eYS

But I'd have to check if I found the id, otherwise findIndex returns -1, and remove would be called like remove (-1) (1) (list) which leads to removing the last item.

Pedro Ávila
@avilapedro
Oct 03 2018 02:55

ap is acting as the S combinator:

const S = f => g => x => f (x) (g (x))

https://gist.github.com/Avaq/1f0636ec5c8d6aed2e45

Pedro Ávila
@avilapedro
Oct 03 2018 03:29

@bmamouri I created a safeRemoveOne, it will only remove if id is gte 0, otherwise it returns the list:

const removeOne = curry ( (idx, list) => remove (idx) (1) (list) )
const safeRemoveOne = ifElse (flip (gte) (0)) (removeOne) (_ => identity) // [1]

const timelineDelete = (state, idx) =>
  ap
    (flip (safeRemoveOne)) // [2]
    (findIndex (propEq ('idx') (idx)))
    (state)

timelineDelete (mystate, 999)
// => [ { idx: 87, name: 'Hello' }, { idx: 54, name: 'World' } ]

[1]: Using ifElse from the crocks library because it allows me to write that onFalseFn (last arg) curried like that, otherwise it'd be: ((_, list) => list)
[2]: Using flip from the crocks library because ramda's flip would call safeRemoveOne like safeRemoveOne (a, b), that doesn't work because safeRemoveOne must be called like safeRemoveOne (a) (b)

REPL: https://repl.it/repls/UprightTepidLead

Baqer Mamouri
@bmamouri
Oct 03 2018 04:10

@avilapedro That's beautiful and definitely elegant. But not that simple to understand. I had to scratch my head several times while reading it! :P

Also appreciate your comments and explanation. Quite educational!

Brad Compton (he/him)
@Bradcomp
Oct 03 2018 04:15
What's wrong with reject(propEq('idx', idx), state)?
@jdkschang the two functions are mirrors of each other. The difference is purely aesthetic.
Pedro Ávila
@avilapedro
Oct 03 2018 04:21
@Bradcomp that's amazingly simpler! But maybe we should have an removeBy function, for not having to iterate through all the items.

I know this sounds like premature optimization, but I see it as a matter of concern, it doesn't make sense to me that reject still iterate over the array after finding the unique item it should reject, it feels to me like using the wrong tool.

But yeah, it's so much simpler that I would use it if I hadn't a removeBy function.

Julien Gonzalez
@customcommander
Oct 03 2018 07:28

@Bradcomp @avilapedro @bmamouri I suppose this example could use chain actually:
You stop iterating once you found the index. Best of both?

function removeBy(fn, state) {
  return chain(remove(__, 1), findIndex(fn))(state);
}

removeBy(propEq('idx', 42), [{idx:1}, {idx:10}, {idx:42}, {idx:123}]);
//=> [{"idx": 1}, {"idx": 10}, {"idx": 123}]

https://goo.gl/oVNpWC

Andrew
@foiseworth
Oct 03 2018 16:21
Is there any equivalent of has for deeply nested objects? Or should I be combining has with propSatisfies?
or rather pathSatisfies
Matthew Willhite
@miwillhite
Oct 03 2018 17:53
It depends on your use case. You could use path to determine if there is a value there or not, but if you need to identify an object because it has that key or not then you’d have to find another strategy
Pedro Ávila
@avilapedro
Oct 03 2018 19:06

@Bradcomp @bmamouri @customcommander

At first glance I couldn't understand chain, after reading Ramda's docs, still I couldn't understand it in the way you're using it (with two fns as first and second args). After reading its source I finally could understand it.

Your implementation is almost the same as mine, using ap as the S combinator. The difference is that my implementation using ap needs to flip the first fn passed to ap in order for it to be able to receive the data first.

Some time ago I asked about a combinator, that supposely didn't exist, I was referring to it as myCombinator: https://gitter.im/ramda/ramda?at=5b998e048909f71f75c580a7

const myCombinator = f => g => x => f (g(x)) (x)

Now I can see by reading chain's source, it is essentially myCombinator (if the snd arg is a fn):

var chain = _curry2(_dispatchable(['fantasy-land/chain', 'chain'], _xchain, function chain(fn, monad) {
  if (typeof monad === 'function') {
    return function(x) { return fn(monad(x))(x); };
  }
  return _makeFlat(false)(map(fn, monad));
}));

https://github.com/ramda/ramda/blob/v0.25.0/source/chain.js#L30

Applying the same example from the docs results in the same:

chain (append) (head) ([1, 2, 3]) //=> [1, 2, 3, 1]
myCombinator (append) (head) ([1, 2, 3]) //=> [1, 2, 3, 1]

// What's really happening:
append ( head ([1, 2, 3]) ) ([1, 2, 3]) //=> [1, 2, 3, 1]

// Using the S combinator (must flip first fn):
ap (flip (append)) (head) ([1, 2, 3]) //=> [1, 2, 3, 1]

Regarding the removeBy fn, @customcommander implementation is essentially the same as mine, although it doesn't handle not found element (just as my first implementation, the last element is dropped):

removeBy(propEq('idx', 50), [{idx:1}, {idx:10}, {idx:42}, {idx:123}])
// => [{"idx": 1}, {"idx": 10}, {"idx": 42}]

Regarding chain I'd like to know where that behavior come from, why was it implemented like that, if there's a name for such a behavior, the doc doesn't mention that behavior (although there's an example).

I'd even say that the doc is confusing since it shows that example but doesn't mention that behavior. Also, the FantasyLand Chain spec mentioned by the doc doesn't mention nothing about the implementation made by Ramda (regarding the first 2 args being fns), unless that "a value that implements the Chain specification must also implement the Apply specification." and "A value which has an Apply must provide an ap method." Is that related somehow? I'm confused about this.

Pedro Ávila
@avilapedro
Oct 03 2018 19:25
Just realized there's already a merged PR adding that to the doc: ramda/ramda#2484 and also a discussion on that: ramda/ramda#2480
Julien Gonzalez
@customcommander
Oct 03 2018 19:45
Yup it’s not perfect I know. Was only pointing out that perhaps chain might have been another option.
Pedro Ávila
@avilapedro
Oct 03 2018 20:30
Yeah, it's actually better than using ap, because you don't have to flip the first fn, I was looking for it without knowing it already had an implementation, but still is hard to me to understand why it is implemented as chain, maybe I should read more about functors, applicatives, types, category theory and all that stuff...
Brandon Chartier
@brandonchartier
Oct 03 2018 20:45
pathOr seems backwards to me: R.pathOr('N/A', ['a', 'b']) vs. R.pathEq(['address', 'zipCode'], 90210)
I guess I expected the path to be the first arg
pathSatisfies too, apparently
Kurt Milam
@kurtmilam
Oct 03 2018 20:53
@avilapedro this might help: https://stackoverflow.com/questions/45786580/ramda-chain-usage (see the accepted answer).
Pedro Ávila
@avilapedro
Oct 03 2018 20:54
@kurtmilam thanks for sharing, I'm gonna have a look at it.
Kurt Milam
@kurtmilam
Oct 03 2018 21:30
@brandonchartier I think the current order of pathOr is more useful if you take into account partial application. For instance, I have some functions like the following using something similar to pathOr:
const pathOrEmptyObject = pathOr({})
const getDataTenant = pathOrEmptyObject(['data', 'tenant'])
const getDataUser = pathOrEmptyObject(['data', 'user'])
I think it makes sense to ask for the default, first, then the path, and then the data.
Pedro Ávila
@avilapedro
Oct 03 2018 22:26

@kurtmilam went through it, it's similar to CrossEye's explanation: https://github.com/ramda/ramda/issues/2480#issuecomment-368544441

But the flow of the explanation: "replace m on the signture to a function, then there's only one way to implement it to work on functions" feels very magical to me.

But I'll just accept it.

Thank you!

Pedro Ávila
@avilapedro
Oct 03 2018 22:35
BTW, is there any way to achieve the same behavior using some ADT? Just for curiosity sake.
Kurt Milam
@kurtmilam
Oct 03 2018 22:43
Which behavior would you like to achieve with an ADT?
Brad Compton (he/him)
@Bradcomp
Oct 03 2018 22:58

I know this sounds like premature optimization, but I see it as a matter of concern, it doesn't make sense to me that reject still iterate over the array after finding the unique item it should reject, it feels to me like using the wrong tool.

I think from the perspective of our data types, it makes sense to use filter / reject instead of removeBy if we are dealing with Array. Array makes no guarantees about uniqueness. If there are two items in the Array with the matching idx wouldn't we want to remove both of them?

If I was in a language like Swift with a propensity for verbosity, then the function removeBy would be better referred to as removeFirstOccurrenceOf because that's what it's actually doing.

If we are using a data type like Set, which guarantees uniqueness, then it makes more sense to have a removeBy function. Another option, if you are sure of uniqueness, is just to use indexBy(prop('idx')) to switch to an object representation, then use dissoc to take care of the id to remove. You could even set up an isomorphism using lenses to switch between the two representations: https://goo.gl/axLDDA

Pedro Ávila
@avilapedro
Oct 03 2018 22:59
@kurtmilam Calling f with g (x) then with x, basically what chain does. But using some ADT with chain as a method, is it possible?