Skip to content

Approximating Immutability Helper with Partial Lenses

Vesa Karvonen edited this page Nov 9, 2017 · 6 revisions

Link to playground

// This is an approximation of
//
//   https://github.com/kolodny/immutability-helper
//
// using Partial Lenses.  A known semantic difference is the
// treatment of `undefined`.  There may be other semantic
// differences that can likely be fixed.

const ops = {
  $push: xs => [L.suffix(0), L.setOp(xs)],
  $unshift: xs => [L.prefix(0), L.setOp(xs)],
  $splice: args => L.seq(args.map(([b, n, ...xs]) => [L.slice(b, b+n), L.setOp(xs)])),
  $set: L.setOp,
  $unset: ps => [L.props(...ps), L.removeOp],
  $merge: L.assignOp,
  $apply: L.modifyOp,
  $toggle: fs => L.seq(...fs.map(f => [f, L.modifyOp(x => !x)]))
}

const toTransform = immutabilityHelper => L.seq(
  ...Object.entries(immutabilityHelper)
  .map(([key, value]) => ops[key] ? ops[key](value)
       : [/^0|[1-9][0-9]*$/.test(key) ? L.ifElse(Array.isArray, Number(key), key) : key,
          toTransform(value)]))

const update = (ds, op) => L.transform(toTransform(op), ds)

update.extend = (key, fn) => ops[key] = arg => L.modifyOp(x => fn(arg, x))

// --- Examples ---

log( 1, update(['x'], {$push: ['y']}) )

log( 2, update({}, {x: {y: {z: {$set: 7}}}, a: {b: {$push: [9]}}}) )

log( 3, update([1, 2, 3], {$push: [4]}) )

log( 4, update([1, 2, {a: [12, 17, 15]}], {2: {a: {$splice: [[1, 1, 13, 14]]}}}) )

log( 5, update({a: 5, b: 3}, {b: {$apply: x => x * 2}}) )

log( 6, update({a: 5, b: 3}, {$merge: {b: 6, c: 7}}) )

update.extend('$auto', (value, object) =>  update(object || {}, value))
update.extend('$autoArray', (value, object) => update(object || [], value))
log( 7, update({}, {
  foo: {$autoArray: {
    0: {$auto: {
      bar: {$autoArray: {$push: ['x', 'y', 'z']}}
    }}
  }}
}))

update.extend('$addtax', (tax, original) => original + (tax * original))
log( 8, update({ price: 123 }, {price: {$addtax: 0.8}}) )

log( 9, update({a: false}, {$toggle: ['a']}) )

Link to playground

// Approximating https://github.com/kolodny/immutability-helper with Partial Lenses

const update = (state, op) => L.transform(op, state)

// --- Operations ---

const $push = xs => [L.suffix(0), L.setOp(xs)]
const $unshift = xs => [L.prefix(0), L.setOp(xs)]
const $splice = (b, n, ...xs) => [L.slice(b, b+n), L.setOp(xs)]
const $set = L.setOp
const $unset = (...ps) => [L.props(...ps), L.removeOp]
const $merge = L.assignOp
const $apply = L.modifyOp

// --- Template object combinator ---

const T = L.branch

// --- Examples ---

const log = console.log

//      update(['x'], {$push: ['y']})
log( 1, update(['x'], $push(['y'])) )

//      update(my, {x: {y: {z: {$set: 7}}}, a: {b: {$push: [9]}}})
log( 2, update({}, T({x: {y: {z: $set(7)}}, a: {b: $push([9])}})) )

//      update([1, 2, 3], {$push: [4]})
log( 3, update([1, 2, 3], $push([4])) )

//      update([1, 2, {a: [12, 17, 15]}], {2: {a: {$splice: [[1, 1, 13, 14]]}}})
log( 4, update([1, 2, {a: [12, 17, 15]}], [2, T({a: $splice(1, 1, 13, 14)})]) )

//      update({a: 5, b: 3}, {b: {$apply: x => x * 2}})
log( 5, update({a: 5, b: 3}, T({b: $apply(x => x * 2)})) )

//      update({a: 5, b: 3}, {$merge: {b: 6, c: 7}})
log( 6, update({a: 5, b: 3}, $merge({b: 6, c: 7})) )

// update({}, {
//   foo: {$autoArray: {
//     0: {$auto: {
//       bar: {$autoArray: {$push: ['x', 'y', 'z']}}
//     }}
//   }}
// })
log( 7, update({}, ["foo", 0, "bar", $push(['x', 'y', 'z'])]) )

// update.extend('$addtax', (tax, original) => original + (tax * original))
const $addTax = tax => $apply(original => original + (tax * original))
//   update({ price: 123 }, {price: {$addtax: 0.8}})
log( 8, update({ price: 123 }, T({price: $addTax(0.8)})) )