Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

Commit 2debf64

Browse files
committed
♻️ PoC navigators with set (#294)
* 🚧 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
1 parent 86951f8 commit 2debf64

15 files changed

+663
-97
lines changed

packages/immutadot-benchmark/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"cross-env": "~5.2.0",
88
"immer": "~1.8.0",
99
"immutable": "~4.0.0-rc.12",
10-
"immutadot": "~1.0.0",
10+
"immutadot": "~2.0.0",
1111
"jest": "~21.2.1",
1212
"lerna": "~3.5.1",
1313
"qim": "~0.0.52"

packages/immutadot-benchmark/src/updateTodos.spec.js

+3-23
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { $each, $slice, set as qimSet } from 'qim'
33

44
import { List, Record } from 'immutable'
55

6-
import immer, { setAutoFreeze, setUseProxies } from 'immer'
6+
import immer, { setAutoFreeze } from 'immer'
77

88
import { createBenchmark } from './benchmark'
99

@@ -70,7 +70,7 @@ function updateTodosList(title, listSize, modifySize, maxTime, maxOperations) {
7070
})
7171
})
7272

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

82-
it('immutable w/ conversion', () => {
83-
benchmark('immutable-toJS', 'immutable 4.0.0-rc.12 (w/ conversion to plain JS objects)', () => {
84-
const [start, end] = randomBounds()
85-
return immutableState.withMutations(state => {
86-
for (let i = start; i < end; i++) state.setIn([i, 'done'], true)
87-
}).toJS()
88-
})
89-
})
90-
9182
it('immer proxy', () => {
9283
benchmark('immer-proxy', 'immer 1.8.0 (proxy implementation w/o autofreeze)', () => {
9384
const [start, end] = randomBounds()
@@ -97,17 +88,6 @@ function updateTodosList(title, listSize, modifySize, maxTime, maxOperations) {
9788
})
9889
})
9990

100-
it('immer ES5', () => {
101-
setUseProxies(false)
102-
benchmark('immer-es5', 'immer 1.8.0 (ES5 implementation w/o autofreeze)', () => {
103-
const [start, end] = randomBounds()
104-
return immer(baseState, draft => {
105-
for (let i = start; i < end; i++) draft[i].done = true
106-
})
107-
})
108-
setUseProxies(true)
109-
})
110-
11191
it('qim', () => {
11292
benchmark('qim', 'qim 0.0.52', () => {
11393
const [start, end] = randomBounds()
@@ -116,7 +96,7 @@ function updateTodosList(title, listSize, modifySize, maxTime, maxOperations) {
11696
})
11797

11898
it('immutad●t', () => {
119-
benchmark('immutadot', 'immutad●t 1.0.0', () => {
99+
benchmark('immutadot', 'immutad●t 2.0.0', () => {
120100
const [start, end] = randomBounds()
121101
return set(baseState, `[${start}:${end}].done`, true)
122102
})

packages/immutadot/src/core/set.js

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { apply } from 'path/apply'
2-
3-
const setOperation = (obj, prop, _, value) => { obj[prop] = value }
1+
import { isString } from 'util/lang'
2+
import { nav } from 'nav/nav'
3+
import { toPath } from '@immutadot/parser'
44

55
/**
66
* Sets the value at <code>path</code> of <code>obj</code>.
7-
* @function
87
* @memberof core
98
* @param {*} obj The object to modify.
109
* @param {string|Array} path The path of the property to set.
@@ -13,6 +12,14 @@ const setOperation = (obj, prop, _, value) => { obj[prop] = value }
1312
* @example set({ nested: { prop: 'old' } }, 'nested.prop', 'new') // => { nested: { prop: 'new' } }
1413
* @since 1.0.0
1514
*/
16-
const set = apply(setOperation)
15+
function set(obj, path, value) {
16+
return nav(toPath(path))(obj).update(() => value)
17+
}
18+
19+
const curried = (path, value) => obj => set(obj, path, value)
20+
21+
function optionallyCurried(...args) {
22+
return isString(args[0]) ? curried(...args) : set(...args)
23+
}
1724

18-
export { set }
25+
export { optionallyCurried as set }
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { isNil, length } from 'util/lang'
2+
import { BaseNav } from './baseNav'
3+
4+
export class ArrayNav extends BaseNav {
5+
get length() {
6+
return length(this.value)
7+
}
8+
9+
copy() {
10+
const { value } = this
11+
if (isNil(value)) return []
12+
return Array.isArray(value) ? [...value] : { ...value }
13+
}
14+
}

packages/immutadot/src/nav/baseNav.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export class BaseNav {
2+
constructor(value, next) {
3+
this.value = value
4+
this._next = next
5+
}
6+
}
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { ArrayNav } from './arrayNav'
2+
import { isNil } from 'util/lang'
3+
4+
class IndexNav extends ArrayNav {
5+
constructor(value, index, next) {
6+
super(value, next)
7+
this._index = index
8+
}
9+
10+
get index() {
11+
const { _index, length } = this
12+
if (_index >= 0) return _index
13+
if (-_index > length) return undefined
14+
return Math.max(length + _index, 0)
15+
}
16+
17+
get next() {
18+
const { _next, index, value } = this
19+
return (isNil(value) || index === undefined) ? _next(undefined) : _next(value[index])
20+
}
21+
22+
get() {
23+
return this.next.get()
24+
}
25+
26+
update(updater) {
27+
const copy = this.copy()
28+
copy[this.index] = this.next.update(updater)
29+
return copy
30+
}
31+
}
32+
33+
export function indexNav(index, next) {
34+
return value => new IndexNav(value, index, next)
35+
}

packages/immutadot/src/nav/nav.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { allProps, index, list, prop, slice } from '@immutadot/parser/consts'
2+
import { indexNav } from './indexNav'
3+
import { propNav } from './propNav'
4+
import { propsNav } from './propsNav'
5+
import { sliceNav } from './sliceNav'
6+
7+
export function nav(path) {
8+
if (path.length === 0) throw new TypeError('path should not be empty')
9+
10+
return path.reduceRight((next, [type, value]) => toNav(type)(value, next), finalNav)
11+
}
12+
13+
function toNav(type) {
14+
switch (type) {
15+
case allProps:
16+
case list:
17+
return propsNav
18+
case index: return indexNav
19+
case prop: return propNav
20+
case slice: return sliceNav
21+
default: throw TypeError(type)
22+
}
23+
}
24+
25+
class FinalNav {
26+
constructor(value) {
27+
this.value = value
28+
}
29+
30+
get() {
31+
return this.value
32+
}
33+
34+
update(updater) {
35+
return updater(this.value)
36+
}
37+
}
38+
39+
function finalNav(value) {
40+
return new FinalNav(value)
41+
}

0 commit comments

Comments
 (0)