Starting code:
const getRanges = compose(
chain(([x, y]) => range(x, y + 1)),
map(compose(map(Number), split('..'))),
match(rangeRegex)
)
const getNumbers = compose(
chain(identity),
map(compose(map(Number), split(','))),
match(rangeNumber)
)
const getValues = val => uniq(concat(getRanges(val), getNumbers(val)))To start simple:
unnest = chain(identity) // usually called join
and
map(compose(f, g)) == compose(map(f), map(f))
so you can do:
const getRanges = compose(
chain(([x, y]) => range(x, y + 1)),
map(Number),
map(split('..')),
match(rangeRegex)
)
const getNumbers = compose(
unnest,
map(Number),
map(split(',')),
match(rangeNumber)
)Which, un-fuses the map, but i suspect the JIT refuses (ha) anyway.
but there is a pattern so I'd do:
const numbersFromDelimettedString = delimiter =>
compose(map(Number), split(delimiter))
const getRanges = compose(
chain(([x, y]) => range(x, y + 1)),
map(numbersFromDelimettedString('..'))
match(rangeRegex)
)
const getNumbers = compose(
unnest,
map(numbersFromDelimettedString(','))
match(rangeNumber)
)About the chain and destructure... Operating on the second in a pair like you do in range(x, y + 1) is map for Tuple.
So you could totally turn that array into a Tuple with an isomorphism for funsies:
const fromArray = ([x, y]) => Tuple(x, y)
const toArray = ({_1:x, _2: y}) => [x, y]Then you could map that y + 1 with inc
const getRanges = compose(
chain(([x, y]) => range(x, y)),
fromArray,
map(inc),
toArray,
map(numbersFromDelimettedString('..'))
match(rangeRegex)
)but that's just apply now in the chain so no destructure needed.
const getRanges = compose(
chain(apply(range)),
fromArray,
map(inc),
toArray,
map(numbersFromDelimettedString('..'))
match(rangeRegex)
)Then you can notice to/from pattern and decide on a number of ways to take advantage of an isomorphism. I prefer dimap
const getRanges = compose(
chain(apply(range)),
dimap(map(inc), fromArray, toArray)
map(numbersFromDelimettedString('..'))
match(rangeRegex)
)But all of this is up to you of course - how far you want to take it
We can use isomorphisms again to solve the getValues issue
The way a function semigroup is defined is to run both functions with x and concat the results
just what we want! But we don't want to mess with the Function.prototype to give it concat
So we can use an isomorphism to lift our function up into our own type Fn, do it's concat, then pull it back down into a normal function:
const Fn = f =>
({
run: f,
concat: ({f: g}) =>
Fn(x => f(x).concat(g(x)))
})
const toF = fn => fn.run
const getValues = compose(uniq, toF(concat(Fn(getRanges), Fn(getNumbers))))I suppose we can unnest that concat call with another dimap, but that's the idea!
In the end, applicative functors are just monoids! So I found this nice and clean solution using Ramda's lift function:
const getValues = compose(uniq, lift(concat)(getRanges, getNumbers))