WELCOME TO

Sébastien Besnier

Functional Programming in JS: don't change anything!

@_sebbes_

WELCOME TO

Sébastien Besnier

Functional Programming in JS: don't change anything!

@_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);

GOAL

RESULT

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
setColor("red");
moveTo(100, 100);
circle(100);

GOAL

RESULT

setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
setColor("blue");
moveTo(150, 50);
circle(50);
// HEAD
setColor("red");
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
setColor("red");
moveTo(100, 100);
circle(100);
setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
setColor("blue");
moveTo(150, 50);
circle(50);
// HEAD
setColor("red");
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
setColor("red");
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

BUG IS EXPENSIVE

User reports bug

BUG IS EXPENSIVE

User reports bug

Product team documents the bug

BUG IS EXPENSIVE

Dev fixes bug

User reports bug

Product team documents the bug

BUG IS EXPENSIVE

Code review

User reports bug

Product team documents the bug

Dev fixes bug

BUG IS EXPENSIVE

Product team checks the bug fix

User reports bug

Product team documents the bug

Dev fixes bug

Code review

BUG IS EXPENSIVE

Product team checks the bug fix

User reports bug

Dev fixes bug

Product team documents the bug

Code review

A lot of
people,
time,
communication
involved

setColor("red");
// LEFT EAR
circle(50);
// RIGHT EAR
moveTo(100, 50);
circle(50);
// HEAD
moveTo(50, 50);
circle(100);
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

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

output parameters:

 

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

output parameters:

* no explicit ones

* 1 hidden: global var mutation

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

output parameters:

* no explicit ones

* 1 hidden: global var mutation

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!

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!

PURE

isLightOn(5) == true
isLightOn(10) == false
isLightOn(21) == true
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!

PURE

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

PUSH SIDE EFFECTS ASIDE!

function setup(config) { 
  window.url = config.url + "&t=42";
  // ...
}

function build_img() {
  const img = new Image();
  img.src = widow.url;
  return img;
}

setup(config);
// ...
build_img();
function setup(config) { 
  window.url = config.url + "&t=42";
  // ...
}

function build_img() {
  const img = new Image();
  img.src = widow.url;
  return img;
}

setup(config);
// ...
build_img();

IMPURE

???

function enrich_config(config) { 
  return {
    url: config.url + "&t=42",
    // ...
  };
}

function build_img(enriched_config) {
  const img = new Image();
  img.src = enriched_config.url;
  return img;
}

enriched_config = enrich_config(config);
build_img(enriched_config);

PURE

function enrich_config(config) { 
  return {
    url: config.url + "&t=42",
    // ...
  };
}

function build_img(enriched_config) {
  const img = new Image();
  img.src = enriched_config.url;
  return img;
}

enriched_config = enrich_config(config);
build_img(enriched_config);

PURE

PUSH SIDE EFFECTS ASIDE!

Notorious impure functions

  • display on screen
  • user input
  • HTTP (or database) request
  • document.getElementById
  • document.body.clientWidth
  • Math.random()

Notorious impure functions

  • display on screen
  • user input
  • HTTP (or database) request
  • document.getElementById
  • document.body.clientWidth
  • Math.random()
  • launchRocket()

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));

ESSENTIAL 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(elements){
  for(const e of elements){
    // ... draw e ...
    
    
    

    
    
    }
  }
}
e = {
    x: 50,
    ...,
    onClick: "leftEarClicked",
}
function draw(elements){
  for(const e of elements){
    // ... draw e ...
    if(e.onClick) {
      
      // ...
      // add appropriate event listener
      // ...
      
    }
  }
}
e = {
    x: 50,
    ...,
    onClick: "leftEarClicked",
}
function draw(elements){
  for(const e of elements){
    // ... draw e ...
    if(e.onClick) {
      addClickListener(evt => {
        if(/* e is clicked */) {
          handle_event(e.onClick);
        }
      });
    }
  }
}
e = {
    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)

IMMEDIATE AND INDIRECT BENEFITS

IMMEDIATE  BENEFITS

PURE CODE IS:

  • EASY TO REASON
    • FOR HUMAN ⇒ LESS BUGS!
    • FOR COMPILERS ⇒ OPTIMIZATIONS!
       
  • TRIVIAL TO TEST
    • same arguments ⇒ same result
    • don't rely on external/implicit configuration

⇒ MORE  MAINTAINABLE  CODE  BASE

INDIRECT  BENEFITS

TIME  TRAVEL DEBUGGER

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

Hey, Mario!
Demo time!

INDIRECT  BENEFITS

ANALYTICS

All relevant user action go through the "update" function

PURITY 

PURE FUNCTION

  • minds their own business
  • same input leads to same output
  • no visible effect from the outside except the returned value

IMMEDIATE  BENEFITS

PURE CODE IS:

  • EASY TO REASON
    • FOR HUMAN ⇒ LESS BUGS!
    • FOR COMPILERS ⇒ OPTIMIZATIONS!
       
  • TRIVIAL TO TEST
    • same arguments ⇒ same result
    • don't rely on external/implicit configuration

⇒ MORE  MAINTAINABLE  CODE  BASE

Security

Security

Being a pirate with NPM

Developer downloads one of your malicious package which runs on the developer's computer

A Node.js server uses a malicious package

A HTML/JS page embeds a malicious package

Leftpad

Leftpad

module.exports = leftpad;

function leftpad (str, len, ch) {
  str = String(str);
  var i = -1;
  if (!ch && ch !== 0) ch = ' ';
  len = len - str.length;
  while (++i < len) { str = ch + str; }
  return str;
}
leftpad("toto", 7, "A") == "AAAtoto"

Leftpad

Leftpad

On March 22, 2016 the author unpublish the package from NPM...
And BOOM !

Leftpad

NPM quickly un-unpublish left-pad

Now in the standard:

"toto".padStart(7, "A") 

... which appeared in most platforms mid-2016

MALICIOUS  NPM  PACKAGES

(For Example Stealing Data)

  • Exacerbated by the proliferation of dependencies

     
  • Possible because can run arbitrary
    side effects
    (on a simple import!)
    • Like in almost all packages manager

MALICIOUS  NPM  PACKAGES

(For Example Stealing Data)

  • Exacerbated by the proliferation of dependencies

     
  • Possible because can run arbitrary
    side effects
    (on a simple import!)
    • Like in almost all packages manager

ELM PACKAGE MANAGER

  • Only exposes pure functions
     
  • Implicit and unwanted side effects
    are impossible : you have to explicitly pass a Cmd to the runtime

WRITE  FUNCTIONAL CODE

 

NOW!

 

WHATEVER  THE LANGUAGE!

"Your talk pretends to speak about Functional Programming and you don't speak about Monads and Functors?

Non mais allô quoi!"

LINKS

Improving Declarative APIs for Graphics with Types

        Philipp Krüger

        https://irreactive.com/declarative-apis

What is Functional Programming

        Kris Jenkins

        http://blog.jenkster.com/2015/12/what-is-functional-programming.html

HOPE YOU ENJOYED YOUR JOURNEY AT

Sébastien Besnier

Functional Programming in JS: don't change anything!

@_sebbes_

BONUSES

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

EFFICIENCY?

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; 
}
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; 
}

IMPURE

PURE

Programmation Fonctionnelle en JS: ne changez rien!

By sebbes

Programmation Fonctionnelle en JS: ne changez rien!

  • 576