These are chat archives for ramda/ramda

3rd
Dec 2018
Harry Solovay
@harrysolovay
Dec 03 2018 01:16
How can I form a predicate that checks to see if a list has a specific prop with a value that contains a certain substring?
Something kinda like this: includes('some-substring', prop('prop-name'))
Harry Solovay
@harrysolovay
Dec 03 2018 01:23
Also, I received an error when trying:
where({
  loader: includes('babel-loader'),
  includes: identity,
}),
I was trying to ensure that the match also requires there to be an 'includes' property defined
Got the following error:
/Users/harrysolovay/Desktop/rescripts/node_modules/ramda/src/internal/_indexOf.js:6
  if (typeof list.indexOf === 'function') {
                  ^
TypeError: Cannot read property 'indexOf' of undefined
    at _indexOf (/Users/harrysolovay/Desktop/rescripts/node_modules/ramda/src/internal/_indexOf.js:6:19)
Brad Compton (he/him)
@Bradcomp
Dec 03 2018 01:28
You could use compose: compose(includes(‘something’), prop
(‘Propnamee’))
Harry Solovay
@harrysolovay
Dec 03 2018 01:30
@Bradcomp awesomee. How can I avoid the error I'm getting? I think the error stems from the predicate being run on types that don't have properties such as loader or includes... I tried wrapping the where(...) predicate with bothand adding a check to make sure it's an object, but that didn't work :(
Brad Compton (he/him)
@Bradcomp
Dec 03 2018 01:31
Otherwise check out propSatisfies maybe?
Hmmm
You could maybe use something like ifElse(is(Object), propSatifies(...), always(false)) or something?
Harry Solovay
@harrysolovay
Dec 03 2018 01:41
Interesting
... a little messy though. Is there any other way?
Brad Compton (he/him)
@Bradcomp
Dec 03 2018 01:45
I’m not sure... you could use like propOr or defaultTo or something
Harry Solovay
@harrysolovay
Dec 03 2018 01:51
Those would be slower
Tried your other suggestion:
ifElse(
      inQuestion => type(inQuestion) === 'Object',
      where({
        loader: includes('babel-loader'),
        includes: identity,
      }),
      F,
    )
Didn't work :(
Anything else @Bradcomp ?
Brad Compton (he/him)
@Bradcomp
Dec 03 2018 01:52
Same error?
Is loader always a string? Is it ever undefined?
Harry Solovay
@harrysolovay
Dec 03 2018 01:55
Loader can be undefined
Also, the predicate might be applied to types that have no props (like booleans and numbers)
So it needs to be able to handle that
I'm gonna be making a lot of predicates like this that inspect the values of objects to see if they meet "sub-predicates" (idk if this is actually a term hahaha)
Being able to compose them in a way that's safe and efficient is important
They'll be used to scan hundreds of objects
So keeping it as efficient as possible is a must
🥳
Brad Compton (he/him)
@Bradcomp
Dec 03 2018 02:02
SO I \made two suggestions. One to solve loader being undefined, and another for when the Whole item is undefined. They will each solve part of your problem. I don't have a very good vision of the overall problem you're trying to solve is, but it sounds like you need to take a look at the data and make a decision based on the shape of what you have, and what you want to end up with.
I think allPass might be able to help you, it allows you to pass in a set of predicates and will short circuit if one returns false
Harry Solovay
@harrysolovay
Dec 03 2018 02:04
Tried that with no success :(
Brad Compton (he/him)
@Bradcomp
Dec 03 2018 02:05
Do you have some tests set up so you can see what is causing your failures?
Harry Solovay
@harrysolovay
Dec 03 2018 02:08
No, but the following works consistently:
o => Boolean(o.loader && o.loader.includes('babel-loader') && o.include)
where babel-loader is the substring I wanna check for
Brad Compton (he/him)
@Bradcomp
Dec 03 2018 02:15
allPass([
  prop('loader'),
  propSatisfies(contains('babel-loader'), 'loader'),
  prop('include')
])
I just edited a dumb typo
Harry Solovay
@harrysolovay
Dec 03 2018 02:18
It workssss!!!!!!!!!!!
THANK YOU @Bradcomp !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Brad Compton (he/him)
@Bradcomp
Dec 03 2018 02:21
:bowtie:
You might consider moving the prop('include') to the top so you drop those immediately
Franz Wong
@franzwong
Dec 03 2018 05:07
Hello, I am looking into pipeWith. I saw the example in the source code. Does it mean f will be executed 3 times?
R.pipeWith(f)([g, h, i])(...args) = f(i, f(h, f(g, ...args)))
I also have the following code (just modified a little bit from another example). It only displays hello twice.
const pipeWhileNotNil = R.pipeWith((f, res) => {
  console.log('hello')
  return R.isNil(res) ? res : f(res)
});
const f = pipeWhileNotNil([Math.pow, R.negate, R.inc])
console.log(f(3, 4))
Alexander Lichter
@manniL
Dec 03 2018 11:50
@franzwong Well, f will only be executed in case res is not Nil in this case. 'hello' will be executed always as it's not coupled to a condition
*"shown" instead of "executed"
Alexander Lichter
@manniL
Dec 03 2018 11:57

I tried to solve the 2nd Part of this years Advent of Code (Day 1) with RamdaJS. Unfortunately I couldn't come up with a "clean" solution.

If anyone as a few spare minutes to look over the code (and possibly give suggestions/feedback how to make things "nicer") I'd really appreciate that!

Link: https://github.com/manniL/advent-of-code/blob/2018/01/index.js#L15-L27
Task: Sum up the inputs as long you reach a interim sum for the second time. When that happens, show it

Stefano Vozza
@svozza
Dec 03 2018 12:27
yeah, i initially thought that would be easy because you’d just need to use scan but then realised that it’s more compliated than that because you could be cycling through the list an arbitrary number of times
Stefano Vozza
@svozza
Dec 03 2018 13:18
in haskell you could probably use cycle and scan to create an infinite list and then look for repeats
Alexander Lichter
@manniL
Dec 03 2018 13:29
@svozza Indeed. Same goes for elixir (cycle the stream and use reduceWhile).
I mean the solution there "works" but is hacky.
Stefano Vozza
@svozza
Dec 03 2018 13:32
you might have to use something with lazy evaluation to do this nicely
maybe transducers?
Alexander Lichter
@manniL
Dec 03 2018 13:37
Unfortunately I haven't looked into transducers yet :see_no_evil:
But it might be a good way to improve the perf from what I know
Franz Wong
@franzwong
Dec 03 2018 13:59
@manniL Thanks for the reply. I want to ask how many times f will be executed, if the number of the functions provided in the array is 3?
Kurt Milam
@kurtmilam
Dec 03 2018 14:00
I see cycle mentioned here and would like to ask - can you think of a name for this function:
const cycle = xs => {
  if ( xs.length <= 1 ) return xs
  const xsCopy = [...xs]
  xsCopy.push ( xsCopy.shift () )
  return xsCopy
}
cycle([1, 2, 3]) // -> [2, 3, 1])
Alexander Lichter
@manniL
Dec 03 2018 14:00
@kurtmilam I'd probably call it rotate[Right/Left] (depending on the direction)
Kurt Milam
@kurtmilam
Dec 03 2018 14:01
@manniL that sounds good. I see that cycle in Haskell does something different and would prefer not to give this function a name that may be confusing.
Alexander Lichter
@manniL
Dec 03 2018 14:13
@kurtmilam I can relate to that. Naming (while keeping an eye on similar methods in other languages) is still hard :see_no_evil:
Kurt Milam
@kurtmilam
Dec 03 2018 14:14
:thumbsup:
I renamed it to rotateLeft and will add a rotateRight if I need one. Trying to decide whether I should eventually have it accept another argument telling how far to rotate, but I only need 1 index at a time right now.
Scott Sauyet
@CrossEye
Dec 03 2018 14:16
const rotateLeft = compose(tail, chain(append, head))
Kurt Milam
@kurtmilam
Dec 03 2018 14:16
rotate that accepts an argument telling how far to rotate and by how many indices (left for positive, right for negative) would also be nice, but that's just gilding the lily at the moment.
@CrossEye that's a nice solution, as well, but I decided to write it in vanilla js, since I see it as a reusable library function :)
Scott Sauyet
@CrossEye
Dec 03 2018 14:18
Oh yes, just remembering that we had a question about that at some point to which chain(append, head) was the answer.
Kurt Milam
@kurtmilam
Dec 03 2018 14:19
I use that behavior of chain (and the similar behavior of ap) pretty often - good stuff :)
e.g.
const normalizeCoordinates =
  R.chain (
    ( [minX, minY] ) =>
      R.map (
        ( [x, y] ) => [x - minX, y - minY]
      )
  )
  ( getMinXY )
This shifts an array of [x, y] coordinates toward the origin ([0, 0]) by calculating the minimum offset from the origin in each axis, then subtracting that minimum from each coordinate :)
Scott Sauyet
@CrossEye
Dec 03 2018 14:22
I also have a new hammer to apply to all the nails, destructuring a default argument based on a required one:
const rotateLeft = (arr, [x, ...xs] = arr) => arr.length ? [...xs, x] : []
Kurt Milam
@kurtmilam
Dec 03 2018 14:23
I do that, too! Makes it a lot easier to avoid return statements.
Riku Tiira
@rikutiira
Dec 03 2018 14:25
Or const rotateLeft = (arr, [x, ...xs] = arr) => arr.length ? xs.push(x) && xs : [] to avoid that one extra copying :)
seeing how it’s library code
Kurt Milam
@kurtmilam
Dec 03 2018 14:26
I've ended up with some long argument definitions, even applying functions inside, like (but spanning several lines):
(x, y = R.add(1, x), z = R.mult(3, y)) => // do something with x and/or y and/or z and return immediately
My co-workers weren't too excited about that, but I like it when used in moderation.
@rikutiira also looks good. I may hang on to my ugly, imperative implementation in case I decide to modify the function to expect a second argument specifying how far and in which direction to rotate.
Kurt Milam
@kurtmilam
Dec 03 2018 14:32
Also, I wonder whether doing ...xs twice will be slower than doing it once and pushing onto the copy.
Alexander Lichter
@manniL
Dec 03 2018 14:33
Is there a more elegant (or point-free) way for:
const valuesInclude = o => R.includes(R.__, R.values(o))

valuesInclude({a: 1})(1) //True
Riku Tiira
@rikutiira
Dec 03 2018 14:42
@manniL I don’t know about more elegant but…
const valuesInclude = R.useWith(R.flip(R.includes), [R.values, R.identity])
it’d be instantly clearer if your valuesIncludetook object as last argument instead of value so there’d be no reason to flip but even so I think aiming for a point-free style here does you more harm than good
Alexander Lichter
@manniL
Dec 03 2018 14:44
@rikutiira Yup, that'd be better. Just realized I need that format a => b => R.includes(a, R.values(b)) anyway as I have to pass it into filter
Kurt Milam
@kurtmilam
Dec 03 2018 14:50
Is there any interest in adding a foldr function in Ramda? I find it really useful in many cases where the iterator function expects data as the last argument. reduceRight doesn't (often) quite do the trick for me:
const foldr = fn => acc => xs => {
  for ( const x of xs ) acc = fn ( x, acc )
  return acc
}
This saves me a bunch of times when I'd otherwise need to flip the iterator.
for instance:
foldr ( append ) ( [] ) ( [1, 2, 3] )
// rather than
reduce ( flip ( append ) ) ( [] ) ( [1, 2, 3] )
Scott Sauyet
@CrossEye
Dec 03 2018 15:08
@manniL: this doesn't look too bad:
const valuesInclude = compose(flip(includes), values)
@kurtmilam: We got rid of aliases a long time ago and "reduce" / "reduceRight" won over "foldl" / "foldr". I would be loathe to bring it back with different functionality. If you think reduceRight is incorrect, we could certainly visit that. Or we could look at this under a different name.
Alexander Lichter
@manniL
Dec 03 2018 15:16
@CrossEye I went with const valuesInclude = R.useWith(R.includes, [R.identity, R.values]) for now :relaxed:
But thanks! :+1:
Kurt Milam
@kurtmilam
Dec 03 2018 15:16
@CrossEye it could be that I'm misunderstanding things, but I think foldr goes from left to right like reduce but calls the iterator with (item, acc) rather than (acc, item).
R.reduceRight switches acc and item, as well, but it moves through the list from right to left.
Array.prototype.reduceRight maintains the acc, item order of reduce, but moves through the list from right to left.
So, I'm aware of two different and incompatible reduceRight implementations, neither of which works like foldr, as I understand it (from Haskell, in this case).
Scott Sauyet
@CrossEye
Dec 03 2018 15:20
Pretty sure Haskell foldr works right to left, foldr :: (a -> b -> b) -> b -> [a] -> b. But I could be wrong.
Kurt Milam
@kurtmilam
Dec 03 2018 15:21
You're probably right there - I may have misunderstood how foldr works.

I'm not sure, though. For instance, from this SO comment:

It helps to understand the distinction between foldr and foldl. Why is foldr called "fold right"?

Initially I thought it was because it consumed elements from right to left. Yet both foldr and foldl consume the list from left to right.

I'm pretty sure I have read that elsewhere, in another article that covers the difference between foldl and foldr in more detail.
Kurt Milam
@kurtmilam
Dec 03 2018 15:27
My understanding is that the rightward folding occurs due to the switching of the order of acc and item in the call to the iterator, and not from consuming the list from right to left, although I read both opinions.
Scott Sauyet
@CrossEye
Dec 03 2018 15:29

http://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html#v:foldr

reduces the list using the binary operator, from right to left:

Kurt Milam
@kurtmilam
Dec 03 2018 15:30
Right, but I don't think that necessarily means it starts with the rightmost element in the array. I will do some research and get back to you :)
Kurt Milam
@kurtmilam
Dec 03 2018 15:38
They do, indeed. Apologies for the confusion :D
Scott Sauyet
@CrossEye
Dec 03 2018 15:57
Not at all. I was only fairly certain because at some point we had to fix reduceRight to match Haskell; it was originally based on the API of Array.prototype.reduceRight. And I was slow to see the point, so someone smarter had to hold my hand through it.
Kurt Milam
@kurtmilam
Dec 03 2018 16:01
Raganwald on reduce, foldl and foldr. The evaluation order is right to left, as you thought.
// my naive, incorrect implementation:
const foldr = fn => acc => xs => {
  for ( const x of xs ) acc = fn ( x ) ( acc )
  return acc
}
// what I believe to be an accurate recursive implementation of foldr:
const foldr1 = fn => acc => xs => {
  if ( xs.length === 0 ) return acc
  const [h, ...t] = xs
  return fn(h)(foldr1(fn)(acc)(t))
}
It starts with head (assuming this is correct - the I get -2 when I evaluate foldr1 ( R.subtract ) ( 0 ) ( [1, 2, 3, 4] ), matching the Haskell results), but the recursion means we're actually evaluating from right to left.
Confusing :D
Alexander Lichter
@manniL
Dec 03 2018 16:04
Is there a "cheap" (in terms of perf) way to remove duplicates from an xprod list (eg. R.xprod([1,2,3,4], [1,2,3,4]) includes [1,2] but also [2,1])? :thought_balloon:
Kurt Milam
@kurtmilam
Dec 03 2018 16:06
Well, 'still starts with head' is incorrect, since it doesn't evaluate fn with head until it's finished evaluating all of the items in tail. It just looks confusingly like it's starting with head if you look at it with a fuzzy head.
Hard to talk about accurately. I should probably shut my clapper :D
Scott Sauyet
@CrossEye
Dec 03 2018 16:27
No, it's good to talk this stuff through. There is always a big disconnect between JS and Haskell because of eager versus lazy. And that makes it really odd, because foldr is the one that can work with lazy lists, but that always feels wrong until I start trying it.
@mannil: I would write a custom version rather than trying to remove them. It's not hard, only ugly.
Alexander Lichter
@manniL
Dec 03 2018 16:29
@CrossEye Thanks for the advice :+1:
Scott Sauyet
@CrossEye
Dec 03 2018 16:44

@mannil: I went to implement something like this, and I realized that I didn't actually understand. I was thinking in terms of indices, so foo([1, 2], ['a', 'b', 'c']) would yield [[1, 'a'], [1, 'b'], [1, 'c'], [2, 'b'], [2, 'c']] like a matrix diagonal.

If what you really want is to worry about duplicated values, so that foo([1, 2, 1], [2, 1, 2]) yields only the four unique combination of 1 and 2 rather than the nine naive values, then I see nothing really better than composing uniq with xprod.

Alexander Lichter
@manniL
Dec 03 2018 16:47
@CrossEye Exactly, I'd only want "half of the matrix". On the other hand, the computation for the whole matrix wasn't as costly as I thought. So I might roll with it :)
(I try to combine a list with itself in this case, so say [1,2,3] should lead to 1,2 1,3 and 2,3 (without 1,1 2,2 3,3 and the inverses (2,1 3,1 3,2))
Scott Sauyet
@CrossEye
Dec 03 2018 16:50
Ok, but that's the easy one to write directly, too:
const distinctPairs = (a, b, [x, ...xs] = a, [y, ... ys] = b) => a.length && b.length
   ? ys.map(y => [x, y]).concat(distinctPairs(xs, ys))
   : []

distinctPairs([1, 2], ['a', 'b', 'c', 'd'])
//=> [[1, "b"], [1, "c"], [1, "d"], [2, "c"], [2, "d"]]
Or if you want to include the main diagonal, change from ys.map(...) to b.map(...).
Alexander Lichter
@manniL
Dec 03 2018 16:54
Neat! Thank you :relaxed:
Harry Solovay
@harrysolovay
Dec 03 2018 19:08

If I define a function (let's call it edit) that takes in 3 arguments:

  1. a unary function that transforms and returns its single argument
  2. a path to part of an object to apply the first argument to
  3. an object that contains the nested props specified in the 2nd argument

And I want to do something like this:

const edit = (transform, pathToPartOfConfig, config) => {
  const lens = lensPath(pathToPartOfConfig)
  return over(lens, transform, config)
}

And then I have another function that uses the newly-defined edit function (let's call this one processEdit):

const processEdit = config =>
  edit(
    c => {
      const newC = doSomethingToC(c)
      return newC
    },
    scanConfigForPath(config),
    config,
  )

As you can see above, it uses the config arg inside of a function scanConfigForPath to find the path which gets used in calling edit.

I'm wondering if there's a way to curry processEdit such that it could go from what it currently is to something like this:

const processEdit =
  edit(
    c => {
      const newC = doSomethingToC(c)
      return newC
    },
    scanConfigForPath(__),
  )

... although the actual solution probably doesn't involve a placeholder for scanConfigForPath... since processEdit applies the same missing argument to two places in the function... I'm just not sure how to do this kind of nested currying.

I know this is a tough one! Sorry... but if anyone is able to help, it would mean a ton to me. Thank you!

Kurt Milam
@kurtmilam
Dec 03 2018 19:25
@harrysolovay it may help to curry edit. then you may be able to do something like:
const processEdit =
  o(
    edit(
      c => {
        const newC = doSomethingToC(c)
        return newC
      )
  )
  (scanConfigForPath)
It's hard to say for sure, though, since I can't really test it out with your example.
Harry Solovay
@harrysolovay
Dec 03 2018 19:27
@kurtmilam I don't see how that would work :( any other ideas?
Kurt Milam
@kurtmilam
Dec 03 2018 19:28
Oh, I see that you need config twice.
chain and ap have some magic for sending a prop in twice when they're called with two functions.
It would be easier with a working example to play around with. I'll see what I can come up with if no one beats me to it.
There's also the W combinator, which calls a wrapped function twice with the next argument.
Harry Solovay
@harrysolovay
Dec 03 2018 19:40
Thank you so so so much!!! I'll check out chain and ap –– @kurtmilam you're the man!
Kurt Milam
@kurtmilam
Dec 03 2018 19:50
converge is probably what you'll need.
Brad Compton (he/him)
@Bradcomp
Dec 03 2018 19:54
c => {
        const newC = doSomethingToC(c)
        return newC
      }
Why is this not just doSomethingToC?
Kurt Milam
@kurtmilam
Dec 03 2018 19:55

Sorry, I deleted that answer because I thought it was wrong, but I think it'll actually work, looking back over your examples:

const processEdit1 =
  converge(
    edit(
      c => {
        const newC = doSomethingToC(c)
        return newC
      }
    ),
    [scanConfigForPath, identity]
  )

edit needs to be curried here.

The W-combinator + o will probably also do the trick here:
const W = fn => x => fn ( x ) ( x )
const edit = curry ( ( fn, x, y ) => `${fn ( x )} ${y}` )
const processEdit =
  W( o ( edit ( toUpper ) ) ( toLower ) )
processEdit( 'Hello' )
// => "HELLO Hello"
Kurt Milam
@kurtmilam
Dec 03 2018 20:01
That's the same result I get with the converge example.
Replace toUpper with your c => function and toLower with your scanConfigForPath function.
Harry Solovay
@harrysolovay
Dec 03 2018 21:55
@Bradcomp and @kurtmilam sorry for the delayed response––just saw this. I'll try this out! THANK YOUUUUU!!!!!!!!!!!!!!!!