I achieved to implement Getter
, Setter
, Lens
, LensAt
(aka At
in monocle) and LensOpt
(aka Optional
in monocle), However, I failed to generalize from Lens
to Traversal
and from Getter
to Fold
by adding an Applicative constraint:
const union = type => (tag, o) => (
o[Symbol.toStringTag] = type,
o.tag = tag.name || tag, o);
const record = (type, o) => (
o[Symbol.toStringTag] = type.name || type, o);
const match = (tx, o) => o[tx.tag] (tx);
// Optics
const Optic = optic => record("Optic", {optic});
const arrLens = i =>
Optic(map => f => xs =>
map(x => arrSet(i) (x) (xs))
(f(xs[i])));
const arrLensAt = i =>
Optic(map => f => xs =>
map(tx =>
match(tx, {
None: _ => arrDel(i) (xs),
Some: ({some: x}) => arrIns(i) (x) (xs)
})) (f(xs[i])));
const arrSetter = i => // TODO: maybe generalize with Settable constraint
Optic(_ => f => xs =>
idMap(x => arrSet(i) (x) (xs))
(f(xs[i])));
const arrGetter = i =>
Optic(map => f => xs =>
map(id)
(f(xs[i])));
const arrLensOpt = i => // also known as Optional for settings w/o sum types
Optic(map => f => xs =>
map(tx =>
match(tx, {
None: _ => xs,
Some: ({some: x}) => arrSet(i) (x) (xs)
})) (f(i < xs.length ? Some(xs[i]) : None)));
// Const type
const Const = _const => record("Const", {const: _const});
const constAp = append => ({const: f}) => ({const: x}) => Const(append(f) (x));
const constOf = empty => _ => Const(empty);
const constMap = _ => ({const: x}) => Const(x);
// Option type
const Option = union("Option");
const None = Option("None", {});
const Some = some => Option(Some, {some});
// Id type
const Id = id => record("Id", {id});
const idAp = ({id: f}) => ({id: x}) => Id(f(x));
const idOf = x => Id(x);
const idMap = f => ({id: x}) => Id(f(x));
// auxiliary functions
const _const = x => _ => x;
const arrSet = i => x => xs =>
xs.slice(0, i)
.concat(x, xs.slice(i + 1));
// MAIN
const xs = [1,2,3,4,5];
const tx = arrLensOpt(3).optic(constMap) (Const) (xs);
const ty = arrLensOpt(10).optic(constMap) (Const) (xs);
const tz = arrLensOpt(2).optic(idMap) (_const(Id(Some(333)))) (xs);
console.log(tx);
console.log(ty);
console.log(tz);
I have no idea how replacing Functor with Applicative allows me to combine several lenses or several getters respectively without changing the shape of the underlying structure (Optic(map => f => xs => ...
) and its type. The Haskell/Scala optic libs I've found so far are rather complex and hard to follow.
const arrTraversal = i =>
Optic(({ap, of}) => f => xs =>
ap(???????)
(f(xs[i])));
const arrFold = i =>
Optic(({ap, of}) => f => xs =>
ap(???????)
(f(xs[i])));
I am not looking for a complete solution but rather for a promising approach.