WELCOME TO

Sébastien Besnier

Fullstack Functional Programming without Monads

@_sebbes_

setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
moveTo(150, 50);
circle(50);
// HEAD
moveTo(100, 100);
circle(100);

GOAL

RESULT

setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
moveTo(150, 50);
circle(50);
// HEAD
moveTo(100, 100);
circle(100);

GOAL

RESULT

setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
setColor("blue");
moveTo(150, 50);
circle(50);
// HEAD
moveTo(100, 100);
circle(100);

ON ACTUAL CODE BASE

50-100 lines

3000 lines

3000 lines

setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
setColor("blue");
moveTo(150, 50);
circle(50);
// HEAD
moveTo(100, 100);
circle(100);
setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
setColor("blue");
moveTo(150, 50);
circle(50);
// HEAD
moveTo(50, 50);
circle(100);

We focus on

ON ACTUAL CODE BASE

setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
setColor("blue");
moveTo(150, 50);
circle(50);
// HEAD
moveTo(50, 50);
circle(100);

We usually don't look at...

We focus on

ON ACTUAL CODE BASE

RESULT

setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
setColor("blue");
moveTo(150, 50);
circle(50);
// HEAD
moveTo(100, 100);
circle(100);

SO THIS HAPPENS
ON ACTUAL CODE BASE

setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
moveTo(100, 50);
circle(50);
// HEAD
moveTo(50, 50);
circle(100);

How many input args for circle?

setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
moveTo(100, 50);
circle(50);
// HEAD
moveTo(50, 50);
circle(100);

3 input parameters:

* 1 explicit: size (100)

* 2 hidden: position and color

How many input args for circle?

setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
moveTo(100, 50);
circle(50);
// HEAD
moveTo(50, 50);
circle(100);

output parameters:

 

3 input parameters:

* 1 explicit: size (100)

* 2 hidden: position and color

How many input args for circle?

setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
moveTo(100, 50);
circle(50);
// HEAD
moveTo(50, 50);
circle(100);

output parameters:

* no explicit ones

* 1 hidden: global var mutation

3 input parameters:

* 1 explicit: size (100)

* 2 hidden: position and color

How many input args for circle?

setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
moveTo(100, 50);
circle(50);
// HEAD
moveTo(50, 50);
circle(100);

output parameters:

* no explicit ones

* 1 hidden: global var mutation

3 input parameters:

* 1 explicit: size (100)

* 2 hidden: position and color

How many input args for circle?

SIDE EFFECT 
==
HIDDEN PARAMETER

LOCAL CODE CHANGES

⇒ GLOBAL BEHAVIOR CHANGES

LOCAL CODE CHANGES

⇒ GLOBAL BEHAVIOR CHANGES

MAINTENANCE
NIGHTMARE

(PURE) DATA

Describe "WHAT" instead of "HOW"

[
  // LEFT EAR
  { pos: [50, 50], color: "red",
    size: 50, shape: "circle"
  },
  // RIGHT EAR
  { pos: [150, 50], color: "red",
    size: 50, shape: "circle"
  },
  // HEAD
  { pos: [100, 100], color: "red",
    size: 100, shape: "circle"
  }
]
[
  // LEFT EAR
  { pos: [50, 50], color: "red",
    size: 50, shape: "circle"
  },
  // RIGHT EAR
  { pos: [150, 50], color: "red",
    size: 50, shape: "circle"
  },
  // HEAD
  { pos: [100, 100], color: "red",
    size: 100, shape: "circle"
  }
]
[
  // LEFT EAR
  { pos: [50, 50], color: "red",
    size: 50, shape: "circle"
  },
  // RIGHT EAR
  { pos: [150, 50], color: "blue",
    size: 50, shape: "circle"
  },
  // HEAD
  { pos: [100, 100], color: "red",
    size: 100, shape: "circle"
  }
]
[
  // LEFT EAR
  { pos: [50, 50], color: "red",
    size: 50, shape: "circle"
  },
  // RIGHT EAR
  { pos: [150, 50], color: "blue",
    size: 50, shape: "circle"
  },
  // HEAD
  { pos: [100, 100], color: "red",
    size: 100, shape: "circle"
  }
]

This only creates a new value.
NOTHING IS DISPLAYED!

draw([
  // LEFT EAR
  { pos: [50, 50], color: "red",
    size: 50, shape: "circle"
  },
  // RIGHT EAR
  { pos: [150, 50], color: "blue",
    size: 50, shape: "circle"
  },
  // HEAD
  { pos: [100, 100], color: "red",
    size: 100, shape: "circle"
  }
])

Only ONE side effect!

draw([
  // LEFT EAR
  { pos: [50, 50], color: "red",
    size: 50, shape: "circle"
  },
  // RIGHT EAR
  { pos: [150, 50], color: "blue",
    size: 50, shape: "circle"
  },
  // HEAD
  { pos: [100, 100], color: "red",
    size: 100, shape: "circle"
  }
])

Only ONE side effect!

PUSH SIDE EFFECTS ASIDE!

PURE FUNCTIONS

Side Effect Free!

function mickey() {
  return [
    { pos: [50, 50], color: "red",
      size: 50, shape: "circle"
    },
    { pos: [150, 50], color: "blue",
      size: 50, shape: "circle"
    },
    { pos: [100, 100], color: "red",
      size: 100, shape: "circle"
    }
  ];
}

draw(mickey());
function mickey(x, y) {
  return [
    { pos: [x, y], color: "red",
      size: 50, shape: "circle"
    },
    { pos: [x+100, y], color: "blue",
      size: 50, shape: "circle"
    },
    { pos: [x+50, y+50], color: "red",
      size: 100, shape: "circle"
    }
  ];
}

draw(mickey(50, 50));
function mickey(x, y) {
  return [
    { pos: [x, y], color: "red",
      size: 50, shape: "circle"
    },
    { pos: [x+100, y], color: "blue",
      size: 50, shape: "circle"
    },
    { pos: [x+50, y+50], color: "red",
      size: 100, shape: "circle"
    }
  ];
}

draw(mickey(50, 50));

This function doesn't have:

  • hidden input
     
  • hidden output
function mickey(x, y) {
  return [
    { pos: [x, y], color: "red",
      size: 50, shape: "circle"
    },
    { pos: [x+100, y], color: "blue",
      size: 50, shape: "circle"
    },
    { pos: [x+50, y+50], color: "red",
      size: 100, shape: "circle"
    }
  ];
}

draw(mickey(50, 50));

PURE FUNCTION

This function doesn't have:

  • hidden input
     
  • hidden output
function isLightOn() {
  var h = (new Date()).getHours();
  return (h < 7) || (h > 19);
}

function isLightOn() {
  var h = (new Date()).getHours();
  return (h < 7) || (h > 19);
}

IMPURE

Very hard to test!

Results depend on current time

function isLightOn() {
  var h = (new Date()).getHours();
  return (h < 7) || (h > 19);
}



function isLightOn(hours) {
  return (hours < 7) || (hours > 19);
}

IMPURE

Very hard to test!

Results depend on current time

function isLightOn() {
  var h = (new Date()).getHours();
  return (h < 7) || (h > 19);
}



function isLightOn(hours) {
  return (hours < 7) || (hours > 19);
}

IMPURE

PURE

isLightOn(5) == true
isLightOn(10) == false
isLightOn(21) == true

Very hard to test!

Results depend on current time

Easy to test!

Results only depend on input args

Pure Functions are

  • Referentially Transparent
  • Easy to Test
  • Easy to Reason about

MOVE MICKEY BY CLICKING ON IT

function mickey(x, y) {
  return [
    { pos: [x, y], color: "red",
      size: 50, shape: "circle"
    },
    { pos: [x+100, y], color: "blue",
      size: 50, shape: "circle"
    },
    { pos: [x+50, y+50], color: "red",
      size: 100, shape: "circle"
    }
  ];
}

draw(mickey(50, 50));
function mickey(x, y) {
  return [
    { pos: [x, y], color: "red",
      size: 50, shape: "circle"
    },
    { pos: [x+100, y], color: "blue",
      size: 50, shape: "circle"
    },
    { pos: [x+50, y+50], color: "red",
      size: 100, shape: "circle"
    }
  ];
}

draw(mickey(50, 50));

STATE

var state = { x : 50, y : 50};

var state = { x : 50, y : 50};

function view(state) {
  return mickey(state.x, state.y);
}
var state = { x : 50, y : 50};

function view(state) {
  return mickey(state.x, state.y);
}

PURE FUNCTION

var state = { x : 50, y : 50};

function view(state) {
  return mickey(state.x, state.y);
}
draw(view(state)); // intial rendering

PURE FUNCTION

var state = { x : 50, y : 50};

function view(state) {
  return mickey(state.x, state.y);
}
draw(view(state)); // intial rendering

function handle_event(event) {
  /* ...computing new_state...
   */
  state = new_state;
  draw(view(state));
}

PURE FUNCTION

function handle_event(event) {
  switch(event) {
      case 'leftEarClicked': 
      	var new_state =
            { ...state, 
              x: state.x - 1
            };
        break;
      //...
  }
  state = new_state;
  draw(view(state));
}
function handle_event(event) {
  switch(event) {
      case 'leftEarClicked': 
      	var new_state =
            { ...state, 
              x: state.x - 1
            };
        break;
      //...
  }
  state = new_state;
  draw(view(state));
}

IMPURE

function handle_event(event) {




  const new_state = update(state, event);




  state = new_state;
  draw(view(state));
}

IMPURE

function handle_event(event) {




  const new_state = update(state, event);




  state = new_state;
  draw(view(state));
}

IMPURE

function update(state, event){
  switch(event) {
    case 'leftEarClicked': 
      return {
        ...state, 
        x: state.x - 1
      };
      //...
  }
}
function handle_event(event) {




  const new_state = update(state, event);




  state = new_state;
  draw(view(state));
}

IMPURE

PURE FUNCTION

function update(state, event){
  switch(event) {
    case 'leftEarClicked': 
      return {
        ...state, 
        x: state.x - 1
      };
      //...
  }
}
function handle_event(event) {
  
  
  const new_state = update(state, event);
  
  
  state = new_state;
  draw(view(state));
}
function handle_event(event) {
  
  
  const new_state = update(state, event);
  
  
  state = new_state;
  draw(view(state));
}

FRAMEWORK PROVIDES...

function handle_event(event) {
  
  
  const new_state = update(state, event);
  
  
  state = new_state;
  draw(view(state));
}

PROGRAMMER DEFINES...

FRAMEWORK PROVIDES...

function handle_event(event) {
  
  
  const new_state = update(state, event);
  
  
  state = new_state;
  draw(view(state));
}

PROGRAMMER DEFINES

PURE FUNCTIONS!

FRAMEWORK PROVIDES...

TRIGGER EVENTS

function mickey(x, y) {
  return [
    { pos: [x, y], color: "red",
      size: 50, shape: "circle"
    },
    { pos: [x+100, y], color: "blue",
      size: 50, shape: "circle"
    },
    { pos: [x+50, y+50], color: "red",
      size: 100, shape: "circle"
    }
  ];
}
function mickey(x, y) {
  return [
    { pos: [x, y], color: "red",
      size: 50, shape: "circle",
      onClick: "leftEarClicked"
    },
    { pos: [x+100, y], color: "blue",
      size: 50, shape: "circle",
      onClick: "rightEarClicked"
    },
    { pos: [x+50, y+50], color: "red",
      size: 100, shape: "circle"
    }
  ];
}
function mickey(x, y) {
  return [
    { pos: [x, y], color: "red",
      size: 50, shape: "circle",
      onClick: "leftEarClicked"
    },
    { pos: [x+100, y], color: "blue",
      size: 50, shape: "circle",
      onClick: "rightEarClicked"
    },
    { pos: [x+50, y+50], color: "red",
      size: 100, shape: "circle"
    }
  ];
}

PURE DATA

PURE DATA

function draw(shapes){
  for(const s of shapes){
    // ... draw s...
    
    
    

    
    
    }
  }
}
s = {
    x: 50,
    ...,
    onClick: "leftEarClicked",
}
function draw(shapes){
  for(const s of shapes){
    // ... draw s ...
    if(s.onClick) {
      
      // ...
      // add appropriate event listener
      // ...
      
    }
  }
}
s = {
    x: 50,
    ...,
    onClick: "leftEarClicked",
}
function draw(shapes){
  for(const s of shapes){
    // ... draw s ...
    if(s.onClick) {
      addClickListener(evt => {
        if(/* s is clicked */) {
          handle_event(s.onClick);
        }
      });
    }
  }
}
s = {
    x: 50,
    ...,
    onClick: "leftEarClicked",
}
function handle_event(event) {
  
  
  const new_state = update(state, event);
  
  
  state = new_state;
  draw(view(state));
}

PROGRAMMER DEFINES

PURE FUNCTIONS!

FRAMEWORK PROVIDES...

THE  ELM  ARCHITECTURE

THE  ELM  ARCHITECTURE

Implemented in (or variations):

  • elm (captain obvious!)
  • Vuex (Vue.js)
  • Redux
  • NgRx/NGXS (Angular)

THE  ELM  ARCHITECTURE

INITIAL STATE

TIME

THE  ELM  ARCHITECTURE

Event 1

INITIAL STATE

TIME

THE  ELM  ARCHITECTURE

Event 1

INITIAL STATE

update(event, state)

TIME

THE  ELM  ARCHITECTURE

Event 1

INITIAL STATE

STATE 2

update(event, state)

TIME

THE  ELM  ARCHITECTURE

Event 1

Event 2

INITIAL STATE

STATE 2

STATE 3

update(event, state)
update(event, state)

TIME

THE  ELM  ARCHITECTURE

Event 1

Event 2

Event 3

INITIAL STATE

STATE 2

STATE 3

STATE 4

update(event, state)
update(event, state)
update(event, state)

TIME

THE  ELM  ARCHITECTURE

Event 1

Event 2

Event 3

Event 4

INITIAL STATE

STATE 2

STATE 3

STATE 4

STATE 5

update(event, state)
update(event, state)
update(event, state)
update(event, state)

TIME

TIME  TRAVEL DEBUGGER

  • out of the box in elm      
  • need some install in other frameworks

Hey, Mario!
Demo time!

DATABASE 101

ID NAME IS ADMIN
1 Alice Yes
2 Bob No
3 Charlie No
4 Daine Yes
5 Evan No
6 Flora No

DATABASE 101

ID NAME IS ADMIN
1 Alice Yes
2 Bob No
3 Charlie No
4 Daine Yes
5 Evan No
6 Flora No

Reflects the STATE of the application

Event

TIME

Event

Event

Event

Event

Event

Event

Event

05:11
05:31
05:45
06:42
06:44
07:02
07:13
07:22

Event

05:05

STORE

Event

TIME

Event

User 4 renamed to "Diane"

Event

Event

Event

Event

Event

05:11
05:31
05:45
06:42
06:44
07:02
07:13
07:22

Event

05:05

STORE

Event

TIME

Event

User 4 renamed to "Diane"

Event

Event

Event

Event

Event

05:11
05:31
05:45
06:42
06:44
07:02
07:13
07:22
05:22

Event

05:05

STATE
at 05:22

My trip at Farcheville




By Daine


Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed aliquam, nisl in scelerisque interdum, ipsum ipsum scelerisque purus, at dapibus sapien eros a odio. Vestibulum feugiat vehicula ipsum, eget facilisis felis sodales quis. Maecenas tincid

STORE

Event

TIME

Event

User 4 renamed to "Diane"

Event

Event

Event

Event

Event

05:11
05:31
05:45
06:42
06:44
07:02
07:13
07:22
06:43

Event

05:05

STATE
at 06:43

My trip at Farcheville




By Diane


Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed aliquam, nisl in scelerisque interdum, ipsum ipsum scelerisque purus, at dapibus sapien eros a odio. Vestibulum feugiat vehicula ipsum, eget facilisis felis sodales quis. Maecenas tincid

STORE

Event

TIME

Event

User 4 renamed to "Diane"

Event

User 3 promoted Admin

Post "I like nazis" published

Post 666 removed

Post 418 removed

05:11
05:31
05:45
06:42
06:44
07:02
07:13
07:22

Event

05:05

PWNED

STORE

Event

TIME

Event

User 4 renamed to "Diane"

Event

User 3 promoted Admin

Post "I like nazis" published

Post 666 removed

Post 418 removed

05:11
05:31
05:45
06:42
06:44
07:02
07:13
07:22

Event

05:05

STORE

Event

TIME

Event

User 4 renamed to "Diane"

Event

Box moved 2px to the right (event id: 12345)

Event

Event

Event

05:11
05:31
05:45
06:42

Event

05:05
07:32
07:33
07:34
07:48

STORE

Event

TIME

Event

User 4 renamed to "Diane"

Event

Box moved 2px to the right (event id: 12345)

Event 12345 ignored (event id: 12346)

Event

Event

05:11
05:31
05:45
06:42
07:32
07:33
07:34
07:48

Event

05:05

CTRL+Z

STORE

Event

TIME

Event

User 4 renamed to "Diane"

Event

Box moved 2px to the right (event id: 12345)

Event 12346 ignored

Event

05:11
05:31
05:45
06:42
07:32
07:33
07:34
07:48

Event

05:05

CTRL+Z

CTRL+SHIFT+Z

STORE

Event 12345 ignored (event id: 12346)

Event

TIME

Event

User 4 renamed to "Diane"

Event

Event

Event

Event

Event

STORE

05:11
05:31
05:45
06:42

Event

05:05

EVENT SOURCING

07:32
07:33
07:34
07:48

Event

TIME

Event

User 4 renamed to "Diane"

Event

Event

Event

Event

Event

05:11
05:31
05:45
06:42

Event

05:05

EVENT SOURCING

Constraining side effects!

07:32
07:33
07:34
07:48

STORE

PERFORMANCE

function handle_event(event) {
  
  
  const new_state = update(state, event);
  
  
  state = new_state;
  draw(view(state));
}
function handle_event(event) {
  
  
  const new_state = update(state, event);
  
  
  state = new_state;
  draw(view(state));
}

Redraw the entire screen

var previous_view = view(init_state);
draw(previous_view);

function handle_event(event) {  
  
  const new_state = update(state, event);
  const new_view = view(new_state);
  
  apply_diff(previous_view, new_view);
  state = new_state;
  previous_view = new_view; 
}
var previous_view = view(init_state);
draw(previous_view);

function handle_event(event) {  
  
  const new_state = update(state, event);
  const new_view = view(new_state);
  
  apply_diff(previous_view, new_view);
  state = new_state;
  previous_view = new_view; 
}

CHEAP

CHEAP

MODERATE

COSTLY

Diff on pixels

Our Toy Implementation

Diff on pixels

Our Toy Implementation

Diff on DOM

Elm, React, Vue

Virtual DOM

Map !

fib = [1,1,2,3,5,8,13,21,34,55];
fib.map(n => n + 1);
1
1
2
3
5
8
13
21
34
55
2
2
3
4
6
9
14
22
35
56

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

fib = [1,1,2,3,5,8,13,21,34,55];
fib.map(n => n + 1);
for(var i = 0; i < fib.length; i++) {
  fib[i] = fib[i] + 1;
}
fib = [1,1,2,3,5,8,13,21,34,55];
fib.map(n => n + 1);
for(var i = 0; i < fib.length; i++) {
  fib[i] = fib[i] + 1;
}

WHAT

HOW

fib = [1,1,2,3,5,8,13,21,34,55];
fib.map(n => n + 1);
fib.map(n => print(n));
fib.map(n => print(n));

IMPURE FUNCTION

→ORDER MATTERS

fib.map(n => n + 1);
1
1
2
3
5
8
13
21
34
55
2
2
3
4
6
9
14
22
35
56

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

PURE FUNCTION

fib.map(n => n + 1);

PURE FUNCTION

1
1
2
3
5
8
13
21
34
55
2
2
3
4
6
9
14
22
35
56

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

Alice

Bob

Charlie

Let's explore other languages!

map (\n -> n + 1) fib
1
1
2
3
5
8
13
21
34
55
2
2
3
4
6
9
14
22
35
56

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

CORE 1

.
.
.

CORE 1000

SPLIT WORK
ON GPU!

map (\n -> n + 1) fib
1
1
2
3
5
8
13
21
34
55
2
2
3
4
6
9
14
22
35
56

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

SEVER 1

.
.
.

SERVER 100

Pure Functions are

  • Referentially Transparent
  • Easy to Test
  • Easy to Reason about

Pure Functions are

  • Referentially Transparent
  • Easy to Test
  • Easy to Reason about
  • Easy to Parallelize
fib.map(n => n + 1);
for(var i = 0; i < fib.length; i++) {
  fib[i] = fib[i] + 1;
}

Array Reallocated

Array Mutated in place

fib.map(n => n + 1);
for(var i = 0; i < fib.length; i++) {
  fib[i] = fib[i] + 1;
}

Array Reallocated

Array Mutated in place

EFFICIENCY ++

fib.map(n => n + 1);
for(var i = 0; i < fib.length; i++) {
  fib[i] = fib[i] + 1;
}

Array Reallocated

Array Mutated in place

EFFICIENCY ++

BUGS ++

Array Reallocated

Array Mutated in place

EFFICIENCY ++

BUGS --

for(var i = 0; i < fib.length; i++) {
  fib[i] = fib[i] + 1;
}

Uniqueness analysis: if fib isn't used then we can reuse the memory

fib.map(n => n + 1);

FUNCTIONAL PROGRAMMING
without monads!

SIDE EFFECTS

PURE FUNCTIONS

Side Effect Free!

  • Referentially Transparent
  • Easy to Test
  • Easy to Reason about
  • Easy to Parallelize

THE  ELM  ARCHITECTURE

EVENT
SOURCING

OPTIMIZATIONS

HOPE YOU ENJOYED YOUR JOURNEY AT

@_sebbes_

Sébastien Besnier

Made with Slides.com