Skip to content

Instantly share code, notes, and snippets.

@kutyel
Last active February 27, 2018 21:01
Show Gist options
  • Save kutyel/42838827154fe85c313ff04497b7520e to your computer and use it in GitHub Desktop.
Save kutyel/42838827154fe85c313ff04497b7520e to your computer and use it in GitHub Desktop.
Tuples, isomorphisms and dimap

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)))

Improvements

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))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment