Fantasy Land

        even death may die 

CC-BY-NC-4.0 / Oct. 2021 / Loïc TRUCHOT

Featuring JavaScript

LE contexte

  • La programmation fonctionnelle est morte en 1970, pendant l'hiver de l'IA
  • JavaScript est un langage WTF, qui doit gérer les callback: un enfer
  • l'API "promesse" est née de ces abysses, remonté doucement à la surface
  • en 20XX le TC39 décide de la vampiriser, de l'avaler
  • une RFC est rédigée
  • une licorne se dresse devant le purgatoire et dit:

Let's begin with an Enigma...

How do you call people that can:

  • create things without physical or material constraints ?
  • manipulate mystical symbols to invoke hosts ?
  • shoot strange names at things to transform them ?
  • love cats ?

NOt witches, but Programmers

  • but still... think about LISP since the 60's
  • or AI todays?
  • What is a program?
    • stack of functions transforming bits
function openEyes () {
  console.log("The truth is coming out");
}
openEyes();

const openMind = () => "It can make you blind.";
const mind = openMind;
const msg = mind();
  • So what should we learn?
    • functions and their secrets are enough: that's the FP approach
    • Let's begin the first ritual....

XOR GATE ?

What dot you see when you think about an exclusive OR ?

 it's not

  • a science
  • about computers hardware
  • a description of "how"

it's

  • magic (an art, a craft)
  • about complexity
  • a description of "what"

SACRIfice Computer science

imperative

declarative

electricity -> transistors -> binary -> assembly -> AST -> language -> symbols

(true && true) && (true && false); // transistor
0b11 + 0b10; // binary
2 + 3; // imperative addition
add(2, 3); // declarative addition

Ritual

Expression over STATEMENT

  • make everything simple...
  • hiding imperative code in blackboxes
    • blackbox: function... as expression
    • details: if, for, var, while, switch... as statements
  • expressions return something -> declarative
  • statements are machine instructions -> imperative

The only secret to controlling complexity is not to focus on the details.

function clone (o) {
    return JSON.parse(JSON.stringify(o));
}
// OR shortly
const clone = (o) => JSON.parse(JSON.stringify(o));

Secret

a wise choice

// imperative, via statements
let statementMessage = '';
if (age < 18) {
  statementMessage = 'You shall not pass!';
} else {
  statementMessage = 'Welcome to the ceremony...';
}
console.log(statementMessage);

// declarative via expressions
function ifElse(cond, ok, notOk) {
  if (cond) { // predicate
    return ok; // consequent
  }
  return notOk; // alternative
}
console.log(ifElse(age < 18, 'Go back !', 'Welcome'));

// AKA ternary, which is an expression too
console.log(age < 18 ? 'Go back !' : 'Welcome');

Become pure

  • What is purity ?
    • the mathematical world is
      • abstract
      • predictable, reasonable 
      • no error
      • finite
      • useless by itself
  • What is impure ?
    • the real world is
      • concrete
      • mostly unpredictable
      • made of errors
      • infinite and mysterious
      • useful by itself

What about a pure function ?

  • predictable: a given input always returns the same output
    • referential transparency
  • no side effects: doesn't modify the world, neither the program itself
    • immutability

 

Ritual

and transparent

avoid side effects

 

  • How to stay pure in this world of tentations?
    • work with constants
    • write very simple functions
      •  
      • ideally, even one-liners
const prop = (key, obj) => obj[key];

Secret

including mutations

const not = (val) => !val;
  • side effects only at start/end of the process
  • think entire program as a function
    • taking inputs
    • returning output / doing a final side effect
    • just like it is...
const nlp = pipe(
  purify, 
  parse, 
  splitWords,
  inferMeaning,
  combineResults
);
nlp("Some dangerous stuff.");

IMPURE / MUTABLE

PURE / IMMUTABLE

let content = 'Turn cat into bat';
const nouns = ['cat', 'owl', 'bat', 'wand'];

function extractNouns() {
  const extractedNouns = [];
  const arr = content.split(' ');
  
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < nouns.length; j++) {
      if (nouns[j] === arr[i]) {
        extractedNouns.push(arr[i]);
      }
    }
  }
  content = extractedNouns;
}

extractNouns();
console.log(content);
const includes = (arr, w) => {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === w) return true;
  }
};

const filterWords = (arr, words) => {
  const filtered = [];
  for (let i = 0; i < words.length; i++) {
    if (includes(arr, words[i])) {
      filtered.push(words[i]);
    }
  }
  return filtered;
};

const extractNouns = (nouns, content) => {
  const words = content.split(' ');
  return filterWords(nouns, words);
};

const content = 'Turn cat into bat';
const nouns = ['cat', 'owl', 'bat', 'wand'];
const result = extractNouns(nouns, content);
console.log(result);

Readability /  Reasonability ?

Name collision?

Value tracking ? Reason about ?

Reusability ? Testing ?

But still get functions dependencies...

Find your PRIMITIVE self

  • Primitives in language define paradigm
  • A basic symbol, usable however you want
  • Haskell/LISP: + or * are primitive expressions, like 3 or "a"
    • +(3, 5) // 8
    • concat("Hello", " ", "World!) // "Hello World!"
  • No difference between "native" ops and custom new functions:
    • combineWeddingVideos("drink.mp4", "dance.mp4") 
  • First class citizen functions
  • Great power, we can:
    • assign them to variables (as values)
    • pass them to other functions (as inputs)
    • return them (as outputs)

Ritual

create your own language

  • JavaScript is multi-paradigm, including FP
  • Here, functions are first class citizen
  • "Released functions" give you expressiveness
  • It's very favorable for Domain Specific Language (DSL)
const avadaKedavra = (human) => `${human} is dead.`;
const episkey = (human) => `${human} is healed.`;

const spells = [avadaKedavra, episkey];

const rand = (arr) => arr[Math.floor(Math.random() * arr.length)];

const me = 'Loïc';
const result = rand(spells)(me);

console.log(result); // dead or alive?

Secret

Join the higher order

Ritual

  • Pass a function to a function leads to abstract a lot more stuff
    • It's named "callback", we already use it all the time in JavaScript
  • What about a forEach to abstract "iteration"?

functions as input or as output

const laugh = () => console.log('Muhahaha...');
document.getElementById('poison').addEventListener('click', laugh);
Promise.resolve('spider web').then((obj) => console.log(`eat ${obj}`));
const forEach = (func, arr) => {
  for (let i = 0; i < arr.length; i++) { 
    func(arr[i]); 
  }
};
forEach(console.log, ['Cthulhu', 'Shoggoth']);
['Cthulhu', 'Shoggoth'].forEach(laugh);
const map = (func, arr) => {
  const newArr = [];
  for (const el of arr) { 
    const newEl = func(el); newArr.push(newEl); 
  }
  return newArr;
};
const eat = (thing) => `I just ate this ${thing}. Tasty.`;
map(eat, ['spider', 'crow']).forEach(console.log), 
forEach(
  (b) => console.log(`${b} blood liters`),
  map((item) => item.blood, [
    { name: 'cat', blood: 2 }, 
    { name: 'snake', blood: 0.4 },
  ]),
);
  • Functions = mappings from a Set to another
  • What about a map function?

{ 1, 2, 3 } -> double -> { 2, 4, 6 }

Closure,  currying

as examples of expressiveness and DRY

  • Keeping some parts "private" is important for hiding details
  • The shared scope between a parent and his child is private
const setOnFire = (a) => {
  const b = 'wood';
  const burn = () => {
    console.log(`${a}is burning in the ${b}`);
  };
  burn();
};
  • b is private, no one can know it
  • a is living in burn
const setOnFire = (a) => {
  const b = 'wood';
  const burn = () => {
    console.log(`${a}is burning in the ${b}`);
  };
  return burn;
};
const makeItBurn = setOnFire('bat');
makeItBurn();
  • What makes a closure useful is it returns the closed scope
  • a and b are "imprisoned" now
const eat = (thing) => `I just ate a ${thing}. Tasty.`;
const forEach = (func) => (arr) => {
  for (let i = 0; i < arr.length; i++) { func(arr[i]); }
};
const map = (func) => (arr) => { /* ... */ };
const logEach = forEach(console.log);
const eatAll = map(eat);
logEach(['spider', 'bat', 'cat', 'owl']);
logEach(eatAll(['Cthulhu', 'Shoggoth']));
  • It's a kind of HOF... a function returning a function
  • It allows partial application...

Secret

DISCARD YOUR HERITAGE

Ritual

Refuse God Objects

[].map(); // OK
Animal.map(); // NOT OK
const beFrightening = a => a + " is frightening";
beFrightening("cat");
beFrightening("sky");
beFrightening("mystery");
  • FP focuses on reusing a small Set of datastructure
  • The List is so important in Computer Science that LISP stands just for List Processing
    • forEach, map, filter, reduce, sort, etc.
  • Sadly, if the function is only a method of "this" object, it's not reusable
  • It's a pain, since a lot of different containers are "mappable"
  • Heritage can handle this problem, but it's limited, and hard to follow
  • That's why FP often chose composition over inheritance
  • Unrelated concept can use the same stuff

COMPOSITION

combining functions

  • The real power is not to bind functions to objects...
  • ...but to compose functions together
const prop = (key) => (obj) => obj[key];
forEach(
  (b) => console.log(`${b} liters`),
)(
  map(prop('blood'))([
    { name: 'cat', blood: 2 }, { name: 'snake', blood: 0.4 },
  ]),
);
  • Oh no! The Pyramid of Doom is back!
const filter = (f) => (arr) => arr.filter(f);
const pipe3 = (f1, f2, f3) => (arg) => {
  f3(f2(f1(arg)));
};
const add = (a) => (b) => a + b;
pipe3(add(111), add(111), console.log)(444);
pipe3(
  filter((item) => item.blood > 0.1),
  map(prop('blood')),
  forEach((b) => console.log(`${b} liters`)),
)([{ name: 'cat', blood: 2 }, { name: 'snake', blood: 0.4 }]);
  • pipe compose curried functions one after the other

Secret

FIND THE TRUE NATURE OF THE UNIVERSE

Ritual

  • Mystics & mathematicians always try to find abstractions representing the logic of the Universe, of Everything
  • The main purpose of Category Theory
  • Something in common between the Set of booleans, and the infinite Set of integers, the unpredictable Set of any strings ?
  • We can "append":
    • a boolean to another: true && false && true
    • an integer to another: 5 + 10 + 5
    • a string to a string : "A " + "real " + "power."
  • There is each time a neutral value: true / 0 / ""
  • We can add parentheses and the result will be the same (associativity)
  • This pattern has a name: Monoid
  • There are other known patterns like that

ISOLATING PATTERNS

  • Monoids are a "type", a trait of some operations related to some Sets
const bools = [true, false, true];
const ints = [1, 2, 3];
console.log(bools[0] && bools[1] && bools[2]);
console.log(ints[0] + ints[1] + ints[2]);
const reduce = (operation, neutral) => (arr) => {
  let acc = neutral;
  for (const el of arr) {
    acc = operation(acc, el);
  }
  return acc;
};
const sum = reduce((a, b) => a + b, 0);
const all = reduce((a, b) => a && b, true);
console.log(sum(ints));
console.log(all(bools));
const max = reduce((a, b) => (a > b ? a : b), -Infinity);
console.log(max(ints));
const pipe = reduce((f, g) => (arg) => g(f(arg)), (a) => a);
const add = (a) => (b) => a + b;
pipe([add(111), add(111), console.log])(444);
  • If operations and neutral values are primitive, we can make a generic function "reducing", "folding" a list of values from a Set.
  • And Monoid is just the beginning...
    • Functor
    • Applicative
    • Monad...
  • Finding the Monad is a philosophical obsession

Secret

KNOW YOURSELF FROM INSIDE

Ritual

  • Lambda Calculus has proven that the function can be the Monad of computation
  • As "Turing-complete" as the Turing Machine itself
  • It implies that anything you find in a computer is replaceable by a function 
  • Even the popular "for" loop...

recursion over loops

const list = ['spider', 'cat', 'bat'];
const recurse = (func, arr) => {
  if (arr.length) {
    const [head, ...tail] = arr;
    func(head);
    recurse(func, tail);
  }
};
recurse(console.log, list);
  • A function calling itself until a condition is reached
  • Because of the "stack" nature of function frames, it's not optimized in every language...

A bright future

I felt a great disturbance in the Force

Java

Ruby

C++

C#

Objective C

Angular

Scala

Elixir

Clojure

Rust

F#

Swift

React

Reason

Elm, PureScript...

Java 8 -> lambda

PHP 7 -> HOF...

LISP

Erlang

OCaml

Haskell

JS & Python ?

Function are alreadyfirst class citizen

Secret

SO welcome to the cult...

 

...and prepare to be hated and burnt

resources & Bibliography

Copy of Functional programming: enter the hidden cult

By Loïc TRUCHOT

Copy of Functional programming: enter the hidden cult

An introduction to Functional Programming with JavaScript, targeting new coders interested by this old paradigm.

  • 428