I started work on an FP library in order to learn this paradigm. I called it funcker - 'func' in functional, plus getting your head around fp can be a bit of a 'funcker'.
Functional programming is one of the original paradigms of computing. Then things moved more towards a physical representation of the world - Object Oriented. Like the circle of life, we are moving back towards the past.
Functions are fast and make good use of disc space. Bruce Lee said be like water. Well, the body is 76% water, yet we are so solid. The idea then is to loosen up, to flow, to become pure and dynamic.
If you have done much TDD (please do!) you will know that as the tests get more specific, the code gets more generic. Same principle here - make the functions generic.
Benefits of fp:
- modularise code, making it more reusable
- reduce the complexity of components
- data flows along a pipeline
- things less likely to break because there is no state or side effects
Nomenclature or Terminology or Symbolic Representations of Concepts
- Function(al)
- An expression related to an input or inputs. Of or relating to this
- Mutable
- Liable to change, from latin 'to change'
- Immutable
- Not changing
- Pure
- Not mixed, sound, free of contamination
- Curry
- Converting 1 function with n arguments into n functions each with 1 argument
- Higher order function
- A function that takes other functions as parameters
- Reactive programming
- Using the 'observable' pattern and treating things as streams and reacting to events in them
- Declarative
- Opposite of imperative, not specifying the exact procedure, using expressions, more like 'map, then filter, then reduce'
- Imperative
- 'specially ordered', opposite of declarative, procedural, writing out everything specifically 'for loop over array, put result in another array, loop again and copy certain values, then loop again and calculate total'
- Side effect
- Changing something outside of itself
- Decomposition
- Decay, but more like breaking into component parts
- Referential transparency
- Given parameters should always return the same values. transparency because it is clear you get the same results, referential because of the parameters used
- Expression
- Anything that results in a value
- State
- The condition something is in at a particular time
- Stateless
- Not in a particular condition that persists
- Arity
- The number of arguments passed to a function. myFunc(1, 2, 3) has an arity of 3
- List comprehension
- Encapsulates the functionality of map and filter into for...of syntax
- Chain(ing)
- Being able to chain functions like a().b().c()
- :: (Has type)
- Standard notation for fp:
function_name :: input_type -> value
Notation
There is a standard notation for functional programming worth covering first:
f :: input -> output
The 'f' is a function, the parameters for that function come after the '::' (or sometimes a single ':'). The '::' means 'has type'. The return value of the function comes after the comma, and the '->' is a function with its returning value. So this notation:
f :: number -> number
Becomes:
const add10 = (x) => x + 10;
Higher order functions
Functions are essentially objects, first class citizens, and can move around as such. Hence we have 'higher order' functions - functions that take other functions as parameters and/or return functions.
Arity
It is best practice that the number of parameters a function takes needs to be a maximum of two. A function really should do one thing and one thing only. Imagine how many branches there would be if a function got 3, 4, 5 or more parameters? That could indicate a complex function - not good.
Functions should do one thing and one thing only. They should not modify anything other than in its own scope.
Closure
The definition of a closure is 'a function object that has a reference to the variables in the context it was created in'.
Mutability
Functions should be pure. This could lead to problems when parameters are references to an object. If that object is changed within a function then a side effect results. Not good.
Monads
A monad is an object.
Functions in the 'funker' example
.curry
If you have ever used bind, then you are half way there. Currying is the same as bind, but applied in a different way. With bind a new function is returned with the 'this' parameter of the original function set to the first parameter of the bind function. Other parameters are partially applied, using closure. The partial application part is exactly what currying is.
Currying takes 1 function with n arguments, and converts it to n functions with 1 argument. We can then create a function that already has most of the data it needs held in a closure, and then we can reuse it.
So instead of:
add(1, 2, 3, 4, 5, 6);
it becomes:
add(1)(2)(3)(4)(5)(6)
That looks a bit weird and not very useful. So to convert a function that usually takes multiple params:
let addToFifteen = funcker.curry(add, 1, 2, 3, 4, 5);
let addToThirty = funcker.curry(add, 10, 10, 10);
console.log(addToFifteen(15)); //30
console.log(addToThirty(15)); //45
.filter
Curried Array.filter
.get
So with imperative coding, to get an attribute for each object in an array we would have to write a for loop, then access the property. Then when we need to do it somewhere else we would write another for loop ...
Not with higher order functions. We simply want to 'get' a property. So we can pass this to a map or forEach like so:
[{ name: 'Billy', age: 42 }, { name: 'Bob', age: 24 }].map(funker.get('name'));
.only
Get a new object with only the fields specified in a space delimited string.
const getNameAge = only('name age');
const userObject = { name: 'Tad', age: 40, secret: 'shhh, don\'t tell anyone' };
console.log(getNameAge(userObject)); // { name: 'Tad', age: 40 }
.compose
Function composition: s: f1 => f2 => f3 => f1(f2(f3(s)))
Takes an array of functions and applies them in reverse order to the supplied value.
compose([f1, f2, f3], value) => f1(f2(f3(value)))