0

How can I use ramda's sequence to traverse a dictionary?

Given the following dictionary

cars = {color: ['yellow', 'red'], year: [2017], model: ['coup', 'sedan']}

I'd like to produce the traversed result

all_cars = [
    {color: 'yellow', year: 2017, model: 'coup'},
    {color: 'yellow', year: 2017, model: 'sedan'},
    {color: 'red', year: 2017, model: 'coup'},
    {color: 'red', year: 2017, model: 'sedan'}
]

Using R.sequence results in a list of an empty list

R.sequence(R.of, cars)
[[]]

If I traverse a list instead of a dictionary it produces the correct cartesian product, but the results are (of course) lists instead of dictionaries.

R.sequence(R.of, [['yellow', 'red'], [2017], ['coup', 'sedan']])
[["yellow", 2017, "coup"], ["yellow", 2017, "sedan"], ["red", 2017, "coup"], ["red", 2017, "sedan"]]
Cirdec
  • 24,019
  • 2
  • 50
  • 100

1 Answers1

1

I can think of two ways to do this, one involving sequence, and the other not.

This one uses your sequence(of) call above:

const convert = lift(map)(
  compose(zipObj, keys), 
  compose(sequence(of), values)
)

const all_cars = convert(cars);

The other was built by my usual technique of keep changing the output with one transformation after another until I get to what I want:

const combine = pipe(
  toPairs,
  map(apply(useWith(map, [objOf, identity]))),
  reduce(xprod, [[]]),
  map(flatten),
  map(mergeAll)
)

const all_cars = combine(cars)

This might be made a little clearer by introducing a crossproduct across multiple lists:

const xproduct = reduce(pipe(xprod, map(unnest)), [[]])

const combine = pipe(
  toPairs,
  map(apply(useWith(map, [objOf, identity]))),
  xproduct,
  map(mergeAll)
)

The second version is what I came up with on first trying the problem. Then when I looked to see what you'd tried, I got that first version. That first version looks cleaner to me, although most of the individual steps in the second one are trivial. But since there is one non-trivial step in there, the first one seems an overall winner.

You can see the first or the second on the Ramda REPL.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • That works, but surely there should be a fantasy-land `Traversable` instance for dictionaries to take advantage of. – Cirdec Nov 14 '17 at 22:08
  • Ramda has [debated this](https://github.com/ramda/ramda/issues/2046) numerous times. It's hard even to get anything foldable against an object when you could have a non-commutative function and you still want to consider `{a:1, b:2}` and `{b:2, a:1}` to be equal. – Scott Sauyet Nov 15 '17 at 02:32
  • Crazy. Considering `{a:1, b:2} = {b:2, a:1}` doesn't preclude choosing a canonical order for `Traversable`. – Cirdec Nov 15 '17 at 08:23
  • 1
    I would like to choose a canonical order, probably by sorting the keys. But the language dictates another order: the order the keys were added. We've simply hit a major sticking point here: some people want to keep the language order, which means our equality needs work. I'd rather keep that value equality and choose a canonical order. It's very hard to get consensus. And I want to fix `keys` and `values` too. – Scott Sauyet Nov 15 '17 at 12:49
  • Or provide more than one `Traversable` instance and a way to tag/wrap dictionaries with the order to use. – Cirdec Nov 16 '17 at 00:35
  • That's an interesting possibility. I'll have to think on that a bit. But it might be a solution to our dilemma. – Scott Sauyet Nov 16 '17 at 14:57