Skip to content

Instantly share code, notes, and snippets.

@outbreak
Last active June 9, 2020 11:13
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save outbreak/39ef717126eb1dd6a4a6dc155e2cd9a8 to your computer and use it in GitHub Desktop.
Save outbreak/39ef717126eb1dd6a4a6dc155e2cd9a8 to your computer and use it in GitHub Desktop.
Functional Lenses
// Operators
const eq = (a) => (b) => a === b
// Types
const typeOf = (a) => typeof a
const isArray = (a) => Array.isArray(a)
const isUndefined = (a) => eq('undefined')(typeOf(a))
// Lists
const reverse = (xs) => xs.reverse()
const reduce = (fn) => (a) => (xs) => xs.reduce(fn, a)
const compose = (...fns) => (x) => reduce((acc, fn) => fn(acc))(x)(reverse(fns))
// Objects
const assign = Object.assign
const prop = (key) => (object) => object[key]
const assoc = (key) => (value) => (object) => assign(isArray(object) ? [] : {}, object, {[key]: value})
// Numbers
const inc = (n) => n + 1
// Lenses
const Lens = (getter) => (setter) => compose(assoc('setter')(setter), assoc('getter')(getter))({})
const Prop = (name) => Lens(prop(name))(assoc(name))
const View = (lens) => (object) => lens.getter(object)
const Set = (lens) => (value) => (object) => lens.setter(value)(object)
const Over = (lens) => (fn) => (object) => lens.setter(fn(lens.getter(object)))(object)
const Combine = (...lenses) => {
const getter = (index) => (object) => isUndefined(object) || eq(index)(lenses.length)
? object
: getter(inc(index))(lenses[index].getter(object))
const setter = (index) => (value) => (object) => eq(index)(lenses.length)
? value
: lenses[index].setter( setter(inc(index))(value)(lenses[index].getter(object)) )(object)
return Lens(getter(0))(setter(0))
}
// Example
const o = {a: {b: {c: 0}}}
const a = Prop('a')
const b = Prop('b')
const d = Prop('d')
const ab = Combine(a, b) // Performs left-to-right lenses combination
console.log(View(ab)(o)) // { c: 0 }
console.log(View(ab)({})) // undefined
console.log(Set(ab)(1)(o)) // { a: { b: 1 } }
console.log(Over(ab)((v) => 10)(o)) // { a: { b: 10 } }
@mk0y
Copy link

mk0y commented Jun 9, 2020

This is fantastic. For compose did you try not doing reverse but just going opposite direction using reduceRight?

const compose = (...fns) => (data) = fns.reduceRight((value, fn) => fn(value), data);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment