Skip to content

Commit

Permalink
♻️ PoC navigators with set (#294)
Browse files Browse the repository at this point in the history
* 🚧 PoC nav

* 🚧 Add index navigator

* 🚧 Add slice navigator

* ♻️ Reorganize code

* ♻️ Move apart get and update logic and use ES6 classes

* 🔥 Remove unused currification level

* ⚡ Use nav in set and validate performance improvement with benchmark

* ✨ Put back negative array index support

* ✅ Report apply tests to nav

* 🚚 Move each navigator in its own file as suggested by @frinyvonnick

* :refactor: Optional currying on set

* ✨ Add all props navigator

* ✨ Prop list navigator

* ♻️ Put TypeError for empty path in nav

* 👌 @frinyvonnick's review
  • Loading branch information
nlepage committed Jan 22, 2019
1 parent 86951f8 commit 2debf64
Show file tree
Hide file tree
Showing 15 changed files with 663 additions and 97 deletions.
2 changes: 1 addition & 1 deletion packages/immutadot-benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"cross-env": "~5.2.0",
"immer": "~1.8.0",
"immutable": "~4.0.0-rc.12",
"immutadot": "~1.0.0",
"immutadot": "~2.0.0",
"jest": "~21.2.1",
"lerna": "~3.5.1",
"qim": "~0.0.52"
Expand Down
26 changes: 3 additions & 23 deletions packages/immutadot-benchmark/src/updateTodos.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { $each, $slice, set as qimSet } from 'qim'

import { List, Record } from 'immutable'

import immer, { setAutoFreeze, setUseProxies } from 'immer'
import immer, { setAutoFreeze } from 'immer'

import { createBenchmark } from './benchmark'

Expand Down Expand Up @@ -70,7 +70,7 @@ function updateTodosList(title, listSize, modifySize, maxTime, maxOperations) {
})
})

it('immutable w/o conversion', () => {
it('immutable', () => {
benchmark('immutable', 'immutable 4.0.0-rc.12 (w/o conversion to plain JS objects)', () => {
const [start, end] = randomBounds()
immutableState.withMutations(state => {
Expand All @@ -79,15 +79,6 @@ function updateTodosList(title, listSize, modifySize, maxTime, maxOperations) {
})
})

it('immutable w/ conversion', () => {
benchmark('immutable-toJS', 'immutable 4.0.0-rc.12 (w/ conversion to plain JS objects)', () => {
const [start, end] = randomBounds()
return immutableState.withMutations(state => {
for (let i = start; i < end; i++) state.setIn([i, 'done'], true)
}).toJS()
})
})

it('immer proxy', () => {
benchmark('immer-proxy', 'immer 1.8.0 (proxy implementation w/o autofreeze)', () => {
const [start, end] = randomBounds()
Expand All @@ -97,17 +88,6 @@ function updateTodosList(title, listSize, modifySize, maxTime, maxOperations) {
})
})

it('immer ES5', () => {
setUseProxies(false)
benchmark('immer-es5', 'immer 1.8.0 (ES5 implementation w/o autofreeze)', () => {
const [start, end] = randomBounds()
return immer(baseState, draft => {
for (let i = start; i < end; i++) draft[i].done = true
})
})
setUseProxies(true)
})

it('qim', () => {
benchmark('qim', 'qim 0.0.52', () => {
const [start, end] = randomBounds()
Expand All @@ -116,7 +96,7 @@ function updateTodosList(title, listSize, modifySize, maxTime, maxOperations) {
})

it('immutad●t', () => {
benchmark('immutadot', 'immutad●t 1.0.0', () => {
benchmark('immutadot', 'immutad●t 2.0.0', () => {
const [start, end] = randomBounds()
return set(baseState, `[${start}:${end}].done`, true)
})
Expand Down
19 changes: 13 additions & 6 deletions packages/immutadot/src/core/set.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { apply } from 'path/apply'

const setOperation = (obj, prop, _, value) => { obj[prop] = value }
import { isString } from 'util/lang'
import { nav } from 'nav/nav'
import { toPath } from '@immutadot/parser'

/**
* Sets the value at <code>path</code> of <code>obj</code>.
* @function
* @memberof core
* @param {*} obj The object to modify.
* @param {string|Array} path The path of the property to set.
Expand All @@ -13,6 +12,14 @@ const setOperation = (obj, prop, _, value) => { obj[prop] = value }
* @example set({ nested: { prop: 'old' } }, 'nested.prop', 'new') // => { nested: { prop: 'new' } }
* @since 1.0.0
*/
const set = apply(setOperation)
function set(obj, path, value) {
return nav(toPath(path))(obj).update(() => value)
}

const curried = (path, value) => obj => set(obj, path, value)

function optionallyCurried(...args) {
return isString(args[0]) ? curried(...args) : set(...args)
}

export { set }
export { optionallyCurried as set }
14 changes: 14 additions & 0 deletions packages/immutadot/src/nav/arrayNav.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { isNil, length } from 'util/lang'
import { BaseNav } from './baseNav'

export class ArrayNav extends BaseNav {
get length() {
return length(this.value)
}

copy() {
const { value } = this
if (isNil(value)) return []
return Array.isArray(value) ? [...value] : { ...value }
}
}
6 changes: 6 additions & 0 deletions packages/immutadot/src/nav/baseNav.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class BaseNav {
constructor(value, next) {
this.value = value
this._next = next
}
}
35 changes: 35 additions & 0 deletions packages/immutadot/src/nav/indexNav.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ArrayNav } from './arrayNav'
import { isNil } from 'util/lang'

class IndexNav extends ArrayNav {
constructor(value, index, next) {
super(value, next)
this._index = index
}

get index() {
const { _index, length } = this
if (_index >= 0) return _index
if (-_index > length) return undefined
return Math.max(length + _index, 0)
}

get next() {
const { _next, index, value } = this
return (isNil(value) || index === undefined) ? _next(undefined) : _next(value[index])
}

get() {
return this.next.get()
}

update(updater) {
const copy = this.copy()
copy[this.index] = this.next.update(updater)
return copy
}
}

export function indexNav(index, next) {
return value => new IndexNav(value, index, next)
}
41 changes: 41 additions & 0 deletions packages/immutadot/src/nav/nav.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { allProps, index, list, prop, slice } from '@immutadot/parser/consts'
import { indexNav } from './indexNav'
import { propNav } from './propNav'
import { propsNav } from './propsNav'
import { sliceNav } from './sliceNav'

export function nav(path) {
if (path.length === 0) throw new TypeError('path should not be empty')

return path.reduceRight((next, [type, value]) => toNav(type)(value, next), finalNav)
}

function toNav(type) {
switch (type) {
case allProps:
case list:
return propsNav
case index: return indexNav
case prop: return propNav
case slice: return sliceNav
default: throw TypeError(type)
}
}

class FinalNav {
constructor(value) {
this.value = value
}

get() {
return this.value
}

update(updater) {
return updater(this.value)
}
}

function finalNav(value) {
return new FinalNav(value)
}

0 comments on commit 2debf64

Please sign in to comment.