These are chat archives for ramda/ramda

30th
Oct 2017
Sten-H
@Sten-H
Oct 30 2017 14:02

I find myself currying composed functions a lot and it forces me to manually apply the last arg as an argument on the compose function

const f = curry((a, b ,c) => compose(g(a), h(b))(c))

Is this commonplace or am I missing something? It feels so awkward and it messes with my IDE a bit

Syaiful Bahri
@syaiful6
Oct 30 2017 14:08
@Sten-H assuming your functions is Ramda style curried, then you just call it directly.. const f = curry((a, b, c) => g(a, h(b, c)), i think that will be equals, but a bit more natural in JS.
Sten-H
@Sten-H
Oct 30 2017 14:09
That is indeed one way, but it can get a bit messy if it's a long compose, maybe I'm just being picky
David Komer
@dakom
Oct 30 2017 15:31
is there a clean way to recursively walk through an object and reduce it? Specifically I have a tree of objects that each contain a property named “foo”, and I want to reduce the entire tree down to replace those objects with their foo. If an object does not contain foo it gets skipped
Tomáš Konrády
@tommmyy
Oct 30 2017 16:18

@Sten-H

Your function:

const f = curry((a, b, c) => compose(g(a), h(b))(c))

is equivalent to

const f2 = useWith(useWith(o, [g, h]), [identity]);

it is not readable, but funny it is :)

Sten-H
@Sten-H
Oct 30 2017 16:20
@tommmyy haha yeah that one is gonna have me scratching my head a week from now
David Komer
@dakom
Oct 30 2017 16:36
okay this seems to solve my problem :) https://goo.gl/7qXiPH
Jan Viktor Apel
@japel
Oct 30 2017 16:43
hey there!
who knows how to do this:
.findKey(obj, .partial(_.isEqual, 'something'));
with ramda?
lodash findKey and partial?
Vasili Sviridov
@vsviridov
Oct 30 2017 16:45
Hello everyone, could anyone point me in the right direction? I want to use Ramda's Lenses with a library called Highland (http://highlandjs.org/). Highland has a way to run transducers (http://highlandjs.org/#transduce), and in my understanding Ramda lenses are transducers? But it doesn't seem to work out of the box.
@japel http://ramdajs.com/docs/#findIndex, nvm that's for arrays
@japel Might want to try a combination of toPairs -> filter -> fromPairs
Jan Viktor Apel
@japel
Oct 30 2017 16:50
thanks... I was just thinking, it would be even easier to flip the object keys and values, so values become keys and keys become values
so invertObj could help, I suppose
that way I dont have to filter and so on, just invert and easy access
Vasili Sviridov
@vsviridov
Oct 30 2017 16:51
but not all JS types are suitable for keys, so it woudln't work for any kind of object
Jan Viktor Apel
@japel
Oct 30 2017 16:52
oh, good catch
bug yeah, in my case it does
thank you anyway
Jonah
@jonahx
Oct 30 2017 18:02
here’s an interesting little ramda puzzle. you have a list, eg 1 2 3 1 2 2 and your task is to replace all instances of 1 2 with 2 1, so that in this example the result would be 2 1 3 2 1 2. how do you do it? (and you’re not allowed to do something silly like convert to string, use a regex, and convert back)
Michael Rosata
@mrosata
Oct 30 2017 18:06
@jonahx
pipe(
  join(','),
  replace(/1,2/g, '2,1'),
  split(','),
  map(parseInt)
)
:) that's just the way I'd do it
by cheating
Jonah
@jonahx
Oct 30 2017 18:07
@mrosata yeah that’s why i added the note. i think it’s interesting because it seems like it should be ridiculously simple, but i haven’t come up with anything i’d consider satisfactory
Vasili Sviridov
@vsviridov
Oct 30 2017 18:25
@japel got pulled into a meeting, but here: https://goo.gl/qHDoaz :D
Michael Rosata
@mrosata
Oct 30 2017 18:31
@jonahx This is where my thinking was going... but it only runs one time, when it hits the first 2, it would have to somehow recurse and check this for each 2 in the array
pipe(
  splitWhen(equals(2)),
  when(
    apply((l, r) => 
      equals(last(l), 1) && equals(head(r), 2)
    ),
    apply((l, r) => 
      concat(update(-1, 2, l), update(0, 1, r))
    )
  )
)
Jonah
@jonahx
Oct 30 2017 18:35
@mrosata keep in the mind the competition here is a fairly straightforward procedural implemenation with 2 temp variables (one for the result, one 2 element store to track when we’ve hit a match) and a short loop. any solution that exceeds that complexity isn’t really worthwhile imo.
Philipp Wille
@Yord
Oct 30 2017 18:47
@jonahx Finally a nice opportunity to use unfold :):
const is12 = o(equals([1, 2]), take(2))

const f = o(
  unnest,
  unfold(
    ifElse(
      o(equals(0), length),
      always(false),
      converge(pair)([
        ifElse(is12, always([2, 1]), take(1)),
        ifElse(is12, drop(2), tail)
      ])
    )
  )
)

const ls = [1, 2, 3, 1, 2, 2]
f(ls) //=> [2, 1, 3, 2, 1, 2]
Philipp Wille
@Yord
Oct 30 2017 18:54
(I try to avoid unfold in the REPL usually, it tends to crash the tab :))
Philipp Wille
@Yord
Oct 30 2017 19:19
Note that you may substitute o(equals(0), length) with isEmpty. Also, although f iterates through the list only once, it is not as performant as a procedural version, since take, drop, and tail use slice internally and create a shallow clone of the array.
Jonah
@jonahx
Oct 30 2017 19:22

@Yord it’s not bad, although i think it’s sort of putting lipstick on the proceduralness. i’m not sure i prefer it, eg, to:

const swap12 = (list, result=[], lastChar=null) =>
  isEmpty(list)
    ? result.concat(lastChar) :
    (lastChar === 1 && list[0] === 2) 
    ? swap12(tail(list), result.concat([2, 1]), null) :
      swap12(tail(list), result.concat(lastChar || []), list[0])

REPL link: https://goo.gl/44Jmci

i still believe there’s a more elegant, more geometric (if you will) way.
conceptually, it feels like adjust, except we’re adjusting pieces of two instead of single elms
Jonah
@jonahx
Oct 30 2017 19:58

Okay, I think I’m approaching something decent:

pipe(
  aperture(2),
  ap(zip, chain( ifElse(equals([1, 2]), T, F) )),
  chain( ifElse(last, always([2, 1]), o(last, head)))
)(ls)

https://goo.gl/ntq2Jb

Philipp Wille
@Yord
Oct 30 2017 19:59
(Hate to bring the bad news, but it breaks with [1, 2, 3, 5, 1, 2, 2])
Jonah
@jonahx
Oct 30 2017 19:59
doh!
Philipp Wille
@Yord
Oct 30 2017 20:00
:(
Jonah
@jonahx
Oct 30 2017 20:00
let me see if i can fix it
Anders Nygren
@litemerafrukt
Oct 30 2017 20:17

I'm new to this fp thing so I gave it a go (better practise :smile: ) I don't know if it counts as all procedural and to long but here goes:

const ls = [1, 2, 3, 1, 2, 2]

const groupCond = (f, s) => f === 1 && s === 2
const groupem = groupWith(groupCond)

const switchPairs = ([f, s]) => [s, f]
const isPair = a => a.length === 2

const switchOnlyPairs = when(isPair, switchPairs)

const switch12 = compose(flatten, map(switchOnlyPairs), groupem)

switch12(ls)

and a repl goo.gl/jq41sp

Jonah
@jonahx
Oct 30 2017 20:23
@litemerafrukt nice job! that’s the best so far imo (and I’ve never used groupWith before, so bonus points). my only small nit is that i think it reads better with no quite so many intermediate names.
Vesa Karvonen
@polytypic
Oct 30 2017 20:24
If I got the requirements right, then this is fairly easy with an indexed map.
You can also fuse the two map operations into a single pass.
Philipp Wille
@Yord
Oct 30 2017 20:25
@litemerafrukt Nice! :) I like your solution best so far!
Jonah
@jonahx
Oct 30 2017 20:28
@polytypic I like your a lot too. In general I don’t love using indexes, but in this case you're merely using them to add the contextual info we want to each element, so the problem can be turned into a map. very nice.
Anders Nygren
@litemerafrukt
Oct 30 2017 20:28
@jonahx @Yord thanks :smile: It is where I'm at in my fp-learning at the moment. Break it down into small bits and try to compose em. I will look at it again to see how to remove intermediates in a nice way.
Vesa Karvonen
@polytypic
Oct 30 2017 20:45
Here is @litemerafrukt 's idea expressed with optics.
Anders Nygren
@litemerafrukt
Oct 30 2017 20:47
@polytypic nice!
have to have a look at those optics
Jonah
@jonahx
Oct 30 2017 20:58

@polytypic here’s your solution converted into a more ramda-esque style:

pipe(
  append(0),
  prepend(0),
  aperture(3),
  map(
    x => 
      equals([1,2], tail(x)) ? 2 :
      equals([1,2], init(x)) ? 1 : x[1]
  )
)(ls)

https://goo.gl/YdePYy
ofc you can ifElse for the map bit, but I find it to be more cluttery than ternaries when you nest them

pentatronix
@pentatronix
Oct 30 2017 21:02
What's the best way to pollute the global namespace with Ramda? Mucking around in the node repl, figured there must be a faster way :)
Vesa Karvonen
@polytypic
Oct 30 2017 21:03
@jonahx Nice! You could also use cond instead of ternaries.
Jonah
@jonahx
Oct 30 2017 21:05
@polytypic good point, i always forget about cond
Jonah
@jonahx
Oct 30 2017 21:11
@polytypic actually, you’ll probably agree not quite as nice in this case, merely because of boilerplate/long-name issues:
pipe(
  append(0),
  prepend(0),
  aperture(3),
  map(
    cond([
      [converge(equals, [always([1,2]), tail]), always(2)],
      [converge(equals, [always([1,2]), init]), always(1)],
      [T, nth(1)]
    ]) 
  )
)(ls)
converge and always need aliases
Vesa Karvonen
@polytypic
Oct 30 2017 21:13
A bit shorter with pipe(tail, equals([1, 2])) (or compose or o if you prefer).
Jonah
@jonahx
Oct 30 2017 21:15
@polytypic yeah that’s better:
pipe(
  append(0),
  prepend(0),
  aperture(3),
  map(
    cond([
      [o(equals([1,2]), tail), always(2)],
      [o(equals([1,2]), init), always(1)],
      [T, nth(1)]
    ]) 
  )
)(ls)
if we had the I combinator i think i’d be happy.
Vesa Karvonen
@polytypic
Oct 30 2017 21:17
SKI
Jonah
@jonahx
Oct 30 2017 21:24
@pentatronix something like R.keys(R).forEach(k => global[k] = R[k]) (untested) should work. replace global with window if in the browser.
pentatronix
@pentatronix
Oct 30 2017 21:25
@jonahx , thanks! That's the one :)
Vesa Karvonen
@polytypic
Oct 30 2017 21:27
Object.assign(global, require('ramda'))
pentatronix
@pentatronix
Oct 30 2017 21:28
Whoa
Jonah
@jonahx
Oct 30 2017 21:38
:bow:
Michael Rosata
@mrosata
Oct 30 2017 21:39
This isn't as elegant as some of the solutions for the 1-2 swap, but it seems to work
const fitsCondition = useWith(and, [o(equals(1), last), equals(2)])

const swap12 = reduce(ifElse(
  fitsCondition,
  o(update(-2, 2), append(1)),
  flip(append)
), [])
Jonah
@jonahx
Oct 30 2017 21:46
@mrosata i think that’s a nice reduce solution, although i basically consider reduce and its cousins to be procedural, conceptually. some disagree, but to my mind it’s a thin coat of paint over a loop with temp variables. whenever possible, i prefer to reconceptualize the problem so i can use a simple map or other simpler, higher-level operator.
Michael Rosata
@mrosata
Oct 30 2017 21:52
@jonahx that's a unique way of looking at reduce, there's no free variables, it's moreso a coat of paint over recursion, imo map and filter are closer to a basic loop than then reduce is. As you say though, some disagree. Almost anything is procedural if you break it down. Otherwise it would be difficult for our human brains to digest
@litemerafrukt I do like your solution. It's pretty good for being new to fp
Jonah
@jonahx
Oct 30 2017 22:02
@mrosata the variables are still there (m, x) and you’re still updating m repeatedly — there may be technical differences, but the way you have to think about it, the number of elements you must keep straight in your mind, is the same as if you were looping and mutating. and you are right, recursion is also the same as both those things (note in my earlier recursive solution the extra arguments which acted as the temp variables). so reduce, temp variables with a loop, recursion with multiple arguments — as far as your brain is concerned, all the same. that’s my contention. you may personally prefer one over the other, your language’s syntax may make one prettier than the other, but that’s just cosmetics. campare to map: you have a single pure transformation. it’s applied to all elements. i don’t have to turn my brain into a little mini state machine to understand that.
also, fwiw, your original “cheat” is still probably the most readable of any of them imo.
Vesa Karvonen
@polytypic
Oct 30 2017 22:06
The way I might put it, when it comes to reduce (or reduceRight), is that it often overspecifies the solution: the elements are to be processed one-by-one in a particular order updating an accumulator on each element.
Jonah
@jonahx
Oct 30 2017 22:08
@polytypic that’s a good point too.
Vesa Karvonen
@polytypic
Oct 30 2017 22:14
Often uses of reduce can be replaced with map + (monoid or semigroup) concat, which gets rid of the overspecification.
Michael Rosata
@mrosata
Oct 30 2017 22:23
if mapping can be used, I will always go there first b/c it's easier as @jonah pointed out the single pure transformation. Composing map operation together to solve a problem, I have to figure out "what do I need to transform these values into in order to get my solution or get to values that can be reduced into my solution". It gets tricky to choose the correct tool when having to consider values not atomic to a single index of the array. Using the correct Monad with a traversal can make it easier to get to the solution (minus the complexities of the Monad being used).
@polytypic This is actually a good example I think, https://www.npmjs.com/package/partial.lenses#L-traverse -- the end traversal is pretty simple and gets the job done very well. The tool (the const State Monad) is fairly complex.
Vesa Karvonen
@polytypic
Oct 30 2017 22:26
Or instead of a monad, preferably an applicative, when sufficient, because a monad, once again, tends to overspecify.
Jonah
@jonahx
Oct 30 2017 22:28
@mrosata yes, occasionally the cure can be worse than the disease, in that you have to do so much work to transform the problem into a simple map (or simple anything), that you may as well not bother. but usually i find that simply means i haven’t hit upon the required simple idea yet. i think the genesis of this swap problem is the perfect example. once i saw @polytypic’s index/map solution, it was instantly clearly that it’s a simple map over an aperture of 3. the preparation was mild too, just bookending the original list with zeros.
Michael Rosata
@mrosata
Oct 30 2017 22:30
@polytypic yea, Applicatives are easier to understand then monads (I guess that shouldn't have to be stated since Monads are applicatives). Is the 1-2 swap something that can be handled with an Applicative?
@jonahx I do like the aperture solution, I wouldn't have been able to come up with that myself. I probably will need to use aperture a few times still before it's something I am comfortable using
Jonah
@jonahx
Oct 30 2017 22:34
@mrosata and even that one, while satisfactory, is a little heavy on implementation detail for my taste. as i said at the beginning, all i’m doing conceptually here is finding “pieces” of the array of length 2, and transforming them (it’s a generalization of adjust or replace in that sense). so ideally my code can express that directly. but i don’t think it can be done with the ramda primitives, so you’d have to make your own utility out of them first, and then solve the problem with that. that’s what i’d probably do if it was a real problem in my codebase. though the aperture solution is right on the borderline of good enough that i might not bother.
Michael Rosata
@mrosata
Oct 30 2017 22:42
@jonahx yea, if your doing something for production and it's a function that you're going to use in compositions especially then it's better most times to write it simple and clean somewhere and import it to where it's needed. Then if ever you need to read the source it's not something that takes time.. something that can be read quick and easy is best my grandpappy used to say. There's a lot of things that I can't express well enough functionally that I end up rewriting in a way that I can read it down the road.
Vesa Karvonen
@polytypic
Oct 30 2017 22:46
BTW, I don't mean this as an absolute view, but nowadays I tend to think that at the point where one starts using a monad, one is no longer doing functional programming. The problem is that while the construction of a monadic operation is referentially transparent, one cannot, in general, reason about the execution (or result) of a monadic operation in a referentially transparent manner. You can mechanically convert any procedural program to a suitable monadic operation and by doing so you are not significantly improving things, IMHO, because the way you need to reason about the monadic operation is essentially the same as the way you need to reason about the procedural program (step-by-step).
pentatronix
@pentatronix
Oct 30 2017 22:56
Anyone happen to know what is going horribly wrong here? Calling the resulting xor on the following snippet fails, and I am a bit lost really :)
const R = require('ramda');
const compose = R.compose;
const split = R.split;
const map = R.map;
const curry = R.curry;

const salt = 'This is your life';

// Repeat our salt indefinitely
function* shaker(){
  while (true) {
    for( let i = 0; i < salt.length; i++ ){
      yield salt[i];
    }
  }
}

// Get our next char from the sequence
const charXOR = curry((salter, a) => a.charCodeAt(0) ^ salter.next().value);

const saltedXOR =  charXOR(shaker());
const mapXORChar = map(saltedXOR);
const fromCharCode = curry(String.fromCharCode.apply)(this);

const xor = compose(fromCharCode, mapXORChar, split(''));


module.exports = {
  xor,
  charXOR,
  shaker
}

it's the curry(String.fromCharCode.apply), and the error is: ```

n.xor("this is a rabbit")
TypeError: Function.prototype.apply was called on #<Object>, which is a object and not a function
at Object.<anonymous> (/home/andre/dev/v4-rest-api/node_modules/ramda/src/internal/_curryN.js:37:27)
at Object.<anonymous> (/home/andre/dev/v4-rest-api/node_modules/ramda/src/internal/_arity.js:10:19)
at Object.<anonymous> (/home/andre/dev/v4-rest-api/node_modules/ramda/src/internal/_pipe.js:3:14)
at Object.xor (/home/andre/dev/v4-rest-api/node_modules/ramda/src/internal/_arity.js:10:19)
at repl:1:3
```

pentatronix
@pentatronix
Oct 30 2017 23:06
Never mind. R.apply()
Vesa Karvonen
@polytypic
Oct 30 2017 23:23
Continuing on that procedural vs functional topic a bit more. It seems to me that one of the many phases (point-free style, CPS, CT, ...) that programmers learning functional programming go through is a pedantic avoidance of procedural mechanisms like assignment. Don't get me wrong. It is very useful for the purpose of learning. However, what really matters is not the micro level properties of code, but the macro level properties of programs. A function (or program component) can be referentially transparent and easy to reason about in the larger picture even if it uses procedural techniques inside. In contrast, a program component that exposes or exhibits procedural techniques (e.g. monadic operations with local state) is not referentially transparent in the larger picture and can make reasoning about the whole much more difficult (step-by-step vs compositional reasoning) even if the component is internally implemented using only purely functional techniques. To me this seems to be one of the most common fundamental mistakes of functional programmers: concentrating almost solely on the micro level properties of their code (e.g. "How to compose this function without using assignment?") rather than the macro level properties of their programs (e.g. "How to compose user interfaces without using local state?"). (Stepping off the soapbox. :smile:)