FP workshop

LeoLanese
2,081 views
undefined

Open Source Your Knowledge, Become a Contributor

Technology knowledge has to be shared and made accessible for free. Join the movement.

Create Content
Previous: Introduction Next: The Pure game.

Functional Programming: What? and Why?

"A journey of a thousand miles begins with a single step." ~ Lao Tzu

What is Functional Programming?

Functional Programming Definition

"Functional programming is a 'declarative' 'paradigm' of building software by 'composing' 'pure functions', avoiding 'shared state', 'mutable data' and 'side-effects'. Placing the mayor emphasis on the use of functions; 'abstracting control flows and operations on data with these functions and threaten them as building blocks' by relying on 'JS first-class' and 'higher-order functions'

So, FP essencially boils down to:

- FP is about pulling programs apart and reassembling them from the same parts, composing in functions together and that means we need to make the output of a function to serve as the input of the next one, in order to do so, we should avoid shared mutable state & side-effects (use pure functions)

"declarative"

FP is a declarative paradigm software development style, like other: IP or OOP, thats **keeps 'functions' and 'data' separate**


"paradigm"

In simple terms, it is a way of thinking about software construction, based on some development style that follow principles.


"composing"

Composition means that we can attach multiple functions together, in a chain, where the return value of the first function becomes the input for the next function

Function Composition

Compose

Compose `f(g(x))`, has a companion funcion: `pipe`, also we have `RxJS Pipe operator` (used by Angular6, etc)

Compose, pipe and RxJS Pipe operator
  • Compose and pipe are similar. What is change is the order:
  • compose: evaluates inside out, <-right-to-left ordown-top`.
  • pipe: evaluates outside in, left-to-right-> or top-down.
  • RxJS pipe method (rxjs/Rx 5.5+ pipeable operators) These operators are pure functions that return the Observable result of all of the operators having been called in the order they were passed in. This can be used as standalone operators instead of methods on an observable.
Compose and pipe example

If you want to compose two functions we can write a compose function:

// defining compose function
const compose = (...functions) => data =>
functions.reduceRight((value, func) => func(value), data);
// defining pipe function
const pipe = (...functions) => data =>
functions.reduce((value, func) => func(value), data);
function n1(){ console.log('n1')}
function n2(){ console.log('n2')}
testCompose = compose(n1, n2);
testPipe = pipe(n1, n2);
console.log(testCompose()); // n2, n1
console.log(testPipe()); // n1, n2
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

LodashFP or use the already available ones in libraries such as LodashFP and Ramda

Compose example using TypeScript
// When multiple decorators apply to a single declaration, their evaluation is similar to function composition in TS
function f() {
    console.log("f(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("f(): called");
    }
}
function g() {
    console.log("g(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("g(): called");
    }
}
function h() { 
    console.log("h(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("h(): called");
    }
}
class C { // some as: f(g(h(x)))
    @h()
    @f()
    @g()
}
// In this model, when composing functions f,g and h the resulting composite (f ∘ g ∘ h)(x) is equivalent to f(g(h(x))).
// 1) The expressions for each decorator are evaluated top-to-bottom.
// h(): evaluated
// f(): evaluated
// g(): evaluated

// 2) The 'results are then called as functions' from bottom-to-top.
// g(): called
// f(): called
// h(): called

Execure TS code (JSfiddle)

RxJS Pipe method example
// old way RxJS 5.5-
import { from } from 'rxjs/observable/from';

const source$ = from([1, 2, 3, 4, 5, 6, 7, 8, 9]);

source$
  .filter(x => x % 2)
  .map(x => x * 2)
  .scan((acc, next) => acc + next, 0)
  .startWith(0)
  .subscribe(x => console.log(x))
// new way RxJS 5.5+ pipeable operators & Angular6+
import { Observable, pipe } from 'rxjs/Rx';
import { filter, map, reduce } from 'rxjs/operators';

// our custom pure functions operators: nice & easy!
const filterOutEvens = filter(x => x % 2) // custom using filter
const doubleBy = x => map(value => value * x); // custom using map
const sum = reduce((acc, next) => acc + next, 0); // custom using reduce

const complicatedLogic = pipe(
  filterOutEvens,
  doubleBy(2),
  sum
);

const source$ = Observable.range(0, 10); // Generates an observable sequence of integral numbers within a specified range

// compose a bunch of pure function operators & pass them as a single operator to an observable
source$
    .let(complicatedLogic)
    .subscribe(x => console.log(x)); // 50

For more information, check out:


"pure functions"

A pure function is a function which:

  • Given the same input, always return the same output.
  • Has no side-effects (immutable)

Pure Functions

Pure functions are tested by building a table of input values and output expectations.

// pure
function add2 (x){
return x + 2;
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// not pure
// impure: because it reads/modifies an external variable, counter, which isn’t local to the function’s scope.
let y = 2;
function adder (x){
return x + y;
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

"shared state"

FP avoids shared state: is stateless. Instead relying on immutable data structures and pure calculation. Thanks to this, functions always give the same output for a given input (the outcome of a function is dependent only on the input and nothing else)

Sharing State problems:

  • Change order:The problem with shared state is that in order to understand the effects of a function, you have to know the entire history of every shared variable that the function uses or affects.
  • Race-condition: A common bug associated to 'share state'is 'race-condition'

With shared state, the order in which function calls are made changes the result of the function calls:

Calling x1(), x2()
// shared-state: 2 -> 3 -> 6
const x = {
val: 2
};
const x1 = () => x.val += 1;
const x2 = () => x.val *= 2;
x1(); // 2 + 1 = 3
x2(); // 3 * 2 = 6
console.log(x.val); // 6
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

This example is exactly equivalent to the above, except the order of the function calls is reversed

Calling x2(), x1()
// shared-state: 2 -> 4 -> 5
const x = {
val: 2
};
const x1 = () => x.val += 1;
const x2 = () => x.val *= 2;
// ...the order of the function calls is reversed...
x2(); // 2 * 2 = 4
x1(); // 4 + 1 = 5
// which changes the resulting value:
console.log(x.val); // 5
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Better avoid shared state, the timing and order of function calls: don’t change the result of calling the function

Avoid shared state = Order it is not important
const x = {
val: 2
};
const x1 = x => Object.assign({}, x, { val: x.val + 1});
const x2 = x => Object.assign({}, x, { val: x.val * 2});
console.log(x1(x2(x)).val); // 5
const y = {
val: 2
};
// Since there are no dependencies on outside variables,
// we don't need different functions to operate on different
// variables.
// Because the functions don't mutate, you can call these
// functions as many times as you want, in any order,
// without changing the result of other function calls.
x2(y);
x1(y);
console.log(x1(x2(y)).val); // 5
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

"mutable data"

An mutable object is any object which can be modified after it's created.

"immutable"

An immutable value or object cannot be changed, so every update creates new value, leaving the old one untouched.

For example, if your application state is immutable, you can save all the states objects in a single store to easily implement undo/redo functionality


"side-effects"

Mutating data can cause unintended side-effects.

A side effect is a change of system state or observable interaction with the outside world that occurs during the calculation of a result.

Side effects may include (but not limited to)

  • Change state
  • Anything that rely on current time, it is impure.
  • Anything that rely on random number
  • Changing the filesystem
  • Inserting a record into a database
  • Making an http call
  • Modifying the DOM tree
  • Accessing system state
  • I/O interaction (user intput, disk access, network access, printing to the screen) is impure throws
// impure function due to use of external non-constants
// The impure function indirectly depends on x
// If you were to change x, then addx would output a different value for the same inputs.
let x = 10
const addx = (a) => a + x
// also impure due to side-effect
const setx = (v) => x = v 

Minimizing moving parts

"abstracting control flows and operations on data with these functions and threaten them as building blocks"

Abstractions hide details (abstract me of the detail) and give us the ability to talk about problems at a higher (or more abstract) level.

// calculate squares
const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
// style using loop
let arraySquares = [];
for(let i = 0; i < array.length; i++) {
arraySquares[i] = Math.pow(array[i], 2);
}
console.log(arraySquares); // [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// minimizing moving parts by abstracting those
array.map(x => Math.pow(x, 2)); // [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

"First-Class-functions" (FcF)

Functions in JavaScript are treated as objects, not just methods: mean that you can STORE functions into a 'variable':

First-Class

The "first-class" means that 'something' has a 'value'

let something = "FP";
console.log(something); // FP
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

First-Class-function

"First-Class Functions” mean that you can STORE functions into a variable

let subName = function(a,b) { return a + b };
console.log(subName); // ƒ(a,b){ return a + b }
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

"Higher-order-functions" (HoF)

In JS, functions are 'First-Class' & 'Higher-Order functions'

Functions are objects: Not just methods: mean that you can STORE functions into a variable, they can be passed around as data, can appear as parameters to other functions (these are usually known as callbacks), and can be returned from other functions (which leads to things like Composable Functions)

// yes we can create functions as many other languages
function foo(n){ return n + 1}
// BUT JS also allow us to create an anonimous function AND assign it to a variable
let HoF = function(n){ return n + 1};
// and then pass the function as a variable and execute it, or not
let HoF2 = HoF;
console.log(HoF(2)); // 3
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// passing f() as arguments
function add(x, y) {
return x + y
}
function subtract(x, y) {
return x - y
}
function callFuncWith2Arguments(arg1, arg2, func) {
return func(arg1, arg2)
}
callFuncWith2Arguments(1, 2, add); // 3
callFuncWith2Arguments(3, 1, subtract); // 2
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

This allow us to have Function Composition: The fact that we can take a function and put into another function, allow us to compose a lot of small functions into bigger functions.


Why?

Benefits of Functional Programming:
  • Functions Stateless: With Pure functions, functions doesn’t have an internal state. It means that all operations it performs are not affected by that state and given the same input parameters and produces the same deterministic output.
// Not using internal state. Deterministic :)
const addPureWay = (v1, v2) => {
return v1 + v2;
};
addPureWay(1, 1); // 2
addPureWay(1, 1); // 2
addPureWay(1, 1); // 2
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  • Deterministic: The great benefit of pure functions is that their output is deterministic: given an input it will always return the same value. This characteristic makes them extremely easy to debug.
// Using internal state. No deterministic :(
// even if the input doesn’t change the impure function can produce different output
const addImpureWay = (() => {
let state = 0; //
return (v) => {
return state += v;
}
})();
addImpureWay(1, 1); // 1
addImpureWay(1, 1); // 2
addImpureWay(1, 1); // 3
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  • Improve the modularity and Reusability: FP treats functions as building blocks by relying on: first-class, higher-order functions to improve the modularity and reusability of your code.

  • Predictability side effects free => Why? Much easer to understand and predict the program behaviour

  • Easy to debug Due to active avoidance of data mutation.

  • Minimizing parts The logic is minimal: less logic = less bugs.

  • Declarative Declarative rather than imperative code (what to do, rather than how to do it)

  • Functional oriented Function composition over imperative flow control

Lego


GO TO TOP

Open Source Your Knowledge: become a Contributor and help others learn. Create New Content