Thinking about Laziness and Immutability in JS
Hello
Bwais and Gals
About me
My name is Robus Gauli and my brother...
Survey Time
Who thinks Javascript is Awesome?
Who thinks Javascript hurts brain cells?
Who thinks both?
Good Parts ?
Asynchronous I/O
High level / "Duck"er Friendly
Functional
Object Oriented / Prototypal
Performant (Thanks to v8)
Bad parts?
Not so good parts?
Really?
Not so good parts?
Callbacks
Promise
Generator
Async / Await
true + undefined
Eden Hazard
Type Hazard
No Static Typing
Forget those.
Let's talk about things that I ask to myself when I look at my Code.
Things I wish was Different.
Things I wish was "Lazy".
But before that
Let me change the background color.
const vals = ["3", "4", "5"]
const result = vals
.map(x => parseInt(x))
.map(x => x * x)
.filter(x => x < 18)
console.log(result); // [9, 16]
Smell a problem?
Let me try again.
["3", "4", "5"]
[9, 16]
f(x)
g(x)
h(x)
[3, 4, 5]
[9, 16, 25]
Now?
f(x)
g(x)
h(x)
[3, 4, 5]
[9, 16, 25]
Heap Memory
Eager Computation
Blocks Event Loop
My goals ?
Don't use memory
Don't perform eager computation
Don't block event loop
Is it possible?
Yes, thanks to generator/coroutine.
But what is Generator?
Generator
Special function that has ability to pause
function* foreva() {
let counter = 0;
while (true) {
yield counter;
counter++
}
}
Solution
function* parseToInt(values) {
for (let val of values) {
yield parseInt(val);
}
}
function* square(values) {
for (let val of parseToInt(values)) {
yield val * val;
}
}
function* filterLessThan18(values) {
for (let val of square(values)) {
if (val > 18) {
continue;
}
yield val;
}
}
const vals = ["3", "4", "5"];
const result = filterLessThan18(vals);
// done but but but
UGLY
EYES BLEED
I need this back
const vals = ["3", "4", "5"]
const result = vals
.map(x => parseInt(x))
.map(x => x * x)
.filter(x => x < 18)
console.log(result); // [9, 16]
Next Big Question?
Don't use memory
Don't perform eager computation
Don't block event loop
And still use my awesome map/filters <3
Is it possible?
Yes, thanks to algebraic structure of a monoid, called a "transformation monoid".
f : X → Y and g : Y → Z then,
g ∘ f : X → Z, (g ∘ f )(x) = g(f(x))
f(x)
g(x)
h(x)
f(g(h(x)))
Transformation
Generator
Solution
import Lazy from 'lazy';
const vals = ["3", "4", "5"]
const result = Lazy(vals)
.map(x => parseInt(x))
.map(x => x * x)
.filter(x => x < 18)
Zero memory footprint
Non Blocking
And I have my awesome map/filters <3
Performant
Any other benefits?
I/O -Callback
CPU Bound
-Lazyness?
f(g(x1))
x1
y1
f(g(x2))
x2
y2
f(g(xn))
xn
yn
Thread 1
Thread 2
Thread n
Concurrent
Parallelism
Wish List
Thread Support
Native support of lazy maps and filters <3
Native parallel run time support for lazy computation
What is possible today?
Zero Heap Memory Allocation
Lazy computation
Fast (GC friendly)
So think about when you write your next map/filter.
Can I do it better?
Think like "Generator Expression" and not "List Comprehension"
;) ;) , Pythonistas?
Lemme Change
Background again...
"Immutability" hmm..
Anyone?
Wikipedia
condition where state cannot be modified after it is created.
My Definition
condition where data is function of time.
f(time) = data
Problems?
return {
...state,
isDirty: true,
data: {
...state.data,
borings: {
...state.data.borings,
[boring]: {
...state.data.borings[boring],
layers,
elevationAndWaterUpdated: true
}
}
}
};
Focus
return {
...state,
isDirty: true,
data: {
...state.data,
borings: {
...state.data.borings,
[boring]: {
...state.data.borings[boring],
layers,
elevationAndWaterUpdated: true
}
}
}
};
Focus
ADD_LAYERS
return {
...staticState,
data: {
...staticState.data,
scenarios: {
...staticState.data.scenarios,
[scenarioId]: {
...staticState.data.scenarios[scenarioId],
staticLayers: {
...staticState.data.scenarios[scenarioId].staticLayers,
[staticLayerId]: {
...staticState.data.scenarios[scenarioId].staticLayers[
staticLayerId
],
isInput: {
...staticState.data.scenarios[scenarioId].staticLayers[
staticLayerId
].isInput,
[name]: value
}
}
}
}
}
}
};
Focus
UPDATE_INPUT_VALUE
return {
...staticState,
data: {
...staticState.data,
scenarios: {
...staticState.data.scenarios,
[scenarioId]: {
...staticState.data.scenarios[scenarioId],
staticLayers: {
...staticState.data.scenarios[scenarioId].staticLayers,
[staticLayerId]: {
...staticState.data.scenarios[scenarioId].staticLayers[
staticLayerId
],
isInput: {
...staticState.data.scenarios[scenarioId].staticLayers[
staticLayerId
].isInput,
[name]: value
}
}
}
}
}
}
};
return {
...state,
isDirty: true,
data: {
...state.data,
borings: {
...state.data.borings,
[boring]: {
...state.data.borings[boring],
layers,
elevationAndWaterUpdated: true
}
}
}
};
Why my every reducers need to know about by redux store?
Questions
What if I change a shape of redux store?
Can I wrap my head around when I or my co-worker see my reducers in future?
Can I maintain it?
Other Problems?
import _cloneDeep from 'lodash/cloneDeep';
const newCopy = _cloneDeep(oldCopy) // Baaam
Solutions
Facebook's
Problem with Immutable
JS
JS
Immutable
Problem with Deep Clone
Original
Copied
Expensive
My goals?
Make Reducers independent of Redux
Don't copy whole universe
Don't reinvent new data structure
Make immutability cheap and fast
And Most Importantly make your code
Readable
Maintainable
Sane
Easy to wrap my brain around
Solution
Use idea of persistence (Clojure)
Use idea of selectors to abstract REDUX
Old State
New State
Referential Sharing
Combining
Persistance &
Selectors
return {
...state,
isDirty: true,
data: {
...state.data,
borings: {
...state.data.borings,
[boring]: {
...state.data.borings[boring],
layers,
elevationAndWaterUpdated: true
}
}
}
};
FROM
TO
return reducer(state)
.set(layers)
.apply();
THIS
return reducer(state)
.set(layers)
.apply();
FROM
return {
...staticState,
data: {
...staticState.data,
scenarios: {
...staticState.data.scenarios,
[scenarioId]: {
...staticState.data.scenarios[scenarioId],
staticLayers: {
...staticState.data.scenarios[scenarioId].staticLayers,
[staticLayerId]: {
...staticState.data.scenarios[scenarioId].staticLayers[
staticLayerId
],
isInput: {
...staticState.data.scenarios[scenarioId].staticLayers[
staticLayerId
].isInput,
[name]: value
}
}
}
}
}
}
};
TO
THIS
return reducer(state)
.set(value)
.apply();
From Hayward Baker
return reducer(state)
.of('#parameters')
.set(parameters)
.of('#isDirty')
.set(true)
.apply();
Use of Referential Sharing
Clean, Maintainable, Easy to my eyes
Reducer is unaware of redux structure
Take aways
Abstract Immutability with Persistence layer
Make your reducers unaware of your redux store
Your reducer should follow "Single Responsibility" Principle
If you are sold
and would like to use
in your Project
yarn add js-immutable
For Contributions
https://github.com/RobusGauli/js-immutable
Upcoming Leaptalk
Nischal Khadka - Automated Test with Spectron
Chumlung - From React-Sheet to Hagrid
Thanks to
Team Members - Nischal, Pratish, Ankita, Chumlung, Prabesh
Leapfrog
And you bwais and gals
Thank you
Copy of lazy
By Sagar Chamling
Copy of lazy
- 182