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