Skip to content

Commit 2edbf7a

Browse files
🚴 perf(append): Avoid recording intermediate states.
This introduces more non-pure methods which can only be used with care. Currently these are prefixed with _UNSAFE_ but maybe _NON_PURE_ would be better? The dev and user experience would certainly be better if we exploited TypeScript types to ensure whatever is passed down to these methods is marked as mutable (including `this`) and whatever comes out of the API is immutable. We could also expose these non-pure methods for special usage but with extreme care. Maybe something like `tree.mutable()._NON_PURE_push(x).immutable();` or `tree.bulk((mutable) => mutable._NON_PURE_push(x));`. There are examples on how to handle this in `funkia/list` and `immutable.js`. Is the _NON_PURE_ prefix really necessary then? The two previous points would allow and exploit the definition of a `VolatileDeep` or `MutableDeep`, not sure on the name yet, whose right and/or left digit is mutable ("or" because we could also settle on `PushableDeep` and `ConsableDeep`). This would allow to replace digits with either specialized mutable digit objects OR with JavaScript Arrays depending on which solution is faster (member assignment + class method table or Array push + switch on Array length).
1 parent e66c944 commit 2edbf7a

File tree

9 files changed

+93
-22
lines changed

9 files changed

+93
-22
lines changed

src/0-core/_fast/_fill_right.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export default function _fill_right(M, left, middle, x1, iterator) {
4747

4848
event = iterator.next();
4949
if (event.done) return new Deep(M, left, middle, new Three(x1, x2, x3));
50-
middle = middle.push(node3(M, x1, x2, x3));
50+
middle = middle._UNSAFE_push(node3(M, x1, x2, x3));
5151
x1 = event.value;
5252
}
5353
}

src/1-digit/1-One.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import assert from 'assert';
22
import {node2, node3} from '../2-node/index.js';
33
import {DigitSplit} from '../0-core/split/DigitSplit.js';
44
import {Single} from '../3-tree/implementations/1-Single.js';
5+
import {Deep} from '../3-tree/implementations/2-Deep.js';
56
import {Digit, Two, Three, Four} from './index.js';
67

78
export function One(a) {
@@ -184,3 +185,14 @@ One.prototype._nodes_with_list_and_four = function (M, list, other) {
184185
One.prototype._list = function () {
185186
return [this.a];
186187
};
188+
189+
One.prototype._isolated_push = function (parent, value) {
190+
assert(parent._right === this);
191+
return new Deep(parent.M, parent._left, parent._middle, this.push(value));
192+
};
193+
194+
One.prototype._UNSAFE_push = function (parent, value) {
195+
assert(parent._right === this);
196+
parent._right = this.push(value);
197+
return parent;
198+
};

src/1-digit/2-Two.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,14 @@ Two.prototype._nodes_with_list_and_four = function (M, list, other) {
207207
Two.prototype._list = function () {
208208
return [this.a, this.b];
209209
};
210+
211+
Two.prototype._isolated_push = function (parent, value) {
212+
assert(parent._right === this);
213+
return new Deep(parent.M, parent._left, parent._middle, this.push(value));
214+
};
215+
216+
Two.prototype._UNSAFE_push = function (parent, value) {
217+
assert(parent._right === this);
218+
parent._right = this.push(value);
219+
return parent;
220+
};

src/1-digit/3-Three.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,17 @@ Three.prototype._nodes_with_list_and_four = function (M, list, other) {
219219
Three.prototype._list = function () {
220220
return [this.a, this.b, this.c];
221221
};
222+
223+
Three.prototype._isolated_push = function (parent, value) {
224+
assert(parent._right === this);
225+
return new Deep(parent.M, parent._left, parent._middle, this.push(value));
226+
};
227+
228+
Three.prototype._UNSAFE_push = function (parent, value) {
229+
assert(parent._right === this);
230+
parent._middle = parent._middle._UNSAFE_push(this._node(parent.M));
231+
// NOTE the following is dangerous if alternating push and init
232+
parent._right = new One(value);
233+
// TODO maybe final output can be fixed
234+
return parent;
235+
};

src/1-digit/4-Four.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,3 +251,20 @@ Four.prototype._nodes_with_list_and_four = function (M, list, other) {
251251
Four.prototype._list = function () {
252252
return [this.a, this.b, this.c, this.d];
253253
};
254+
255+
Four.prototype._isolated_push = function (parent, value) {
256+
assert(parent._right === this);
257+
return new Deep(
258+
parent.M,
259+
parent._left,
260+
parent._middle.push(this.init()._node(parent.M)),
261+
new Two(this.last(), value),
262+
);
263+
};
264+
265+
Four.prototype._UNSAFE_push = function (parent, value) {
266+
assert(parent._right === this);
267+
parent._middle = parent._middle._UNSAFE_push(this.init()._node(parent.M));
268+
parent._right = new Two(this.last(), value);
269+
return parent;
270+
};

src/3-tree/implementations/0-Empty.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ Empty.prototype.init = function () {
3838
return this;
3939
};
4040

41-
Empty.prototype.push = function (value) {
42-
return new Single(this.M, value);
43-
};
41+
Empty.prototype._UNSAFE_push =
42+
// eslint-disable-next-line no-multi-assign
43+
Empty.prototype.push = function (value) {
44+
return new Single(this.M, value);
45+
};
4446

4547
Empty.prototype.cons = function (value) {
4648
return new Single(this.M, value);
@@ -72,6 +74,10 @@ Empty.prototype.split = function (_p) {
7274
return [this, this];
7375
};
7476

77+
Empty.prototype._copy_spine = function () {
78+
return this;
79+
};
80+
7581
Empty.prototype._concat_with_deep = function (other) {
7682
assert(other instanceof Deep);
7783
assert(isSameMeasure(other.M, this.M));

src/3-tree/implementations/1-Single.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,16 @@ Single.prototype.cons = function (value) {
4949
);
5050
};
5151

52-
Single.prototype.push = function (value) {
53-
return new Deep(
54-
this.M,
55-
new One(this.a),
56-
new Empty(cache(this.M)),
57-
new One(value),
58-
);
59-
};
52+
Single.prototype._UNSAFE_push =
53+
// eslint-disable-next-line no-multi-assign
54+
Single.prototype.push = function (value) {
55+
return new Deep(
56+
this.M,
57+
new One(this.a),
58+
new Empty(cache(this.M)),
59+
new One(value),
60+
);
61+
};
6062

6163
Single.prototype.append = function (iterable) {
6264
const it = iterable[Symbol.iterator]();
@@ -95,6 +97,10 @@ Single.prototype.split = function (p) {
9597
: [this, new Empty(this.M)];
9698
};
9799

100+
Single.prototype._copy_spine = function () {
101+
return this;
102+
};
103+
98104
Single.prototype._concat_with_deep = function (other) {
99105
assert(other instanceof Deep);
100106
assert(isSameMeasure(other.M, this.M));

src/3-tree/implementations/2-Deep.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -112,16 +112,11 @@ Deep.prototype.cons = function (value) {
112112
};
113113

114114
Deep.prototype.push = function (value) {
115-
if (this._right instanceof Four) {
116-
return new Deep(
117-
this.M,
118-
this._left,
119-
this._middle.push(this._right.init()._node(this.M)),
120-
new Two(this._right.last(), value),
121-
);
122-
}
115+
return this._right._isolated_push(this, value);
116+
};
123117

124-
return new Deep(this.M, this._left, this._middle, this._right.push(value));
118+
Deep.prototype._UNSAFE_push = function (value) {
119+
return this._right._UNSAFE_push(this, value);
125120
};
126121

127122
Deep.prototype.append = function (iterable) {
@@ -139,7 +134,9 @@ Deep.prototype.append = function (iterable) {
139134
const middle = _append_small_list(
140135
this._middle,
141136
this._right._nodes(this.M, new One(a)),
142-
);
137+
)._copy_spine();
138+
// TODO _copy_spine should return a MutableDeep type on the spine
139+
// TODO then _fill_right should only accept Empty, Single, or MutableDeep
143140

144141
return _fill_right(this.M, this._left, middle, b, it);
145142
};
@@ -225,6 +222,10 @@ Deep.prototype._concat_with_deep = function (other) {
225222
);
226223
};
227224

225+
Deep.prototype._copy_spine = function () {
226+
return new Deep(this.M, this._left, this._middle._copy_spine(), this._right);
227+
};
228+
228229
Deep.prototype._app3 = function (list, other) {
229230
assert(other instanceof Tree);
230231
return other._app3_with_deep(list, this);

src/4-lazy/0-Lazy.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ Lazy.prototype.push = function (value) {
4040
return this._force().push(value);
4141
};
4242

43+
Lazy.prototype._UNSAFE_push = function (value) {
44+
return this._force()._UNSAFE_push(value);
45+
};
46+
4347
Lazy.prototype.tail = function () {
4448
return this._force().tail();
4549
};

0 commit comments

Comments
 (0)