# Professor Frisby's Mostly Adequate Guide to Functional Programming https://mostly-adequate.gitbook.io/mostly-adequate-guide/ # Motivation? * state and mutable values are hard to follow * we can solve this by composing small functions together and delay the state mutations until we are ready to do it * another coding style to learn * you will finally understand what functional Hitler was talking about (https://www.youtube.com/watch?v=ADqLBc1vFwI) ## First class functions We can treat functions as any other data type ```javascript const hi = name => `Hi ${name}; const greeting = name => hi(name); ``` Assign them to variable, pass as an arguments to other functions... #### Beware of needless wrapping Don't wrap functions in functions when it isn't needed. ```javascript! // ignorant const getServerStuff = callback => ajaxCall(json => callback(json)); // enlightened const getServerStuff = ajaxCall; ``` Can be refactored into following: ```javascript! // this line ajaxCall(json => callback(json)); // is the same as this line ajaxCall(callback); // so refactor getServerStuff const getServerStuff = callback => ajaxCall(callback); // ...which is equivalent to this const getServerStuff = ajaxCall; // <-- look mum, no ()'s ``` The benefit is, we don't have to change the wrapper function when the wrapped function changes it's parameters. We need to be sure we know the arguments the function is passing though. ```js! array.map(parseInt) ``` The map passes value and index while the parse function takes value and radix as params. ### Generic naming The first example ties ourselves to a specific data - it is easy to have the same function several times in our code with only the name of the parameter changed. While the second example defines a function that can be easily reused. ```javascript! // specific to our current blog const validArticles = articles => articles.filter(article => article !== null && article !== undefined), // vastly more relevant for future projects const compact = xs => xs.filter(x => x !== null && x !== undefined); ``` ### Be aware of `this` If an underlying function uses `this` and we call it first class, because it is different based on the context. ### Pure functions A pure function is a function that, given the same input, will always return the same output and does not have any observable side effect. Pure functions summary with examples: https://www.youtube.com/watch?v=fYbhD_KMCOg `splice` is pure because it returns new array, while `splice` modifies the array it was given. ```javascript! const xs = [1,2,3,4,5]; // pure xs.slice(0,3); // [1,2,3] xs.slice(0,3); // [1,2,3] xs.slice(0,3); // [1,2,3] // impure xs.splice(0,3); // [1,2,3] xs.splice(0,3); // [4,5] xs.splice(0,3); // [] ``` In functional programming, we dislike impure functions, because we want to have reliable functions that return the same result every time. In the following example, the first function is impure, because it depends on the system state to determine the result (mutable variable `minimum`). Reliance on system state is one of the largest contributors to system complexity. ```javascript! // impure let minimum = 21; const checkAge = age => age >= minimum; // pure const checkAge = (age) => { const minimum = 21; return age >= minimum; }; ``` We can make minimum immutable: ```javascript! const immutableState = Object.freeze({ minimum: 21 }); ``` ### 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 are not limited to: * changing the file system * inserting a record into a database * making an http call * mutations * printing to the screen / logging * obtaining user input * querying the DOM * accessing system state We want to contain side effects and run them in a controlled way. Generally we compose functions with side effects together and call it when we are ready to pull the trigger ### The Case for Purity #### Cacheable Pure functions can be always cached by input -> memoization ```javascript! const squareNumber = memoize(x => x * x); squareNumber(4); // 16 squareNumber(4); // 16, returns cache for input 4 squareNumber(5); // 25 squareNumber(5); // 25, returns cache for input 5 ``` We can cache every pure function, no matter how destructive it can be. #### Portable Pure functions are completely self contained, everything the function needs is handed to it. The functions dependencies need to be explicit => easier to understand ```javascript! // impure const signUp = (attrs) => { const user = saveUser(attrs); welcomeUser(user); }; // pure const signUp = (Db, Email, attrs) => () => { const user = saveUser(Db, attrs); welcomeUser(Email, user); }; ``` Notice that we are forced to inject dependencies or pass them in as arguments. When was the last time you copied a method into a new app? One of my favorite quotes comes from Erlang creator, Joe Armstrong: "The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana... and the entire jungle". #### Testable Pure functions are much easier to test - we don't have to mock anything. Just give the function an input and assert the output. See Quickcheck - tool for testing pure function ### Parallel Code We can run any pure function in parallel because it does not need access to shared memory. ## Currying The concept is simple: You can call a function with fewer arguments than it expects. It returns a function that takes the remaining arguments. ```javascript! const add = x => y => x + y; const increment = add(1); const addTen = add(10); increment(2); // 3 addTen(2); // 12 ``` Here we've made a function add that takes one argument and returns a function. By calling it, the returned function remembers the first argument from then on via the closure. Currying is the process of taking a function with multiple arguments and turning it into a sequence of functions each with only a single argument. Named after Haskell Curry. ```javascript! const notCurry = (x, y, z) => x + y + z; // a regular function const curry = x => y => z => x + y + z; // a curry function ``` ## Curry implementation ```javascript! function curry(fn) { const arity = fn.length; return function $curry(...args) { if (args.length < arity) { return $curry.bind(null, ...args); } return fn.call(null, ...args); }; } ``` ## Compose Compose allows us to structure smaller functions together like a lego bricks, which leads to reusability. ```javascript const compose = (f, g) => x => f(g(x)); ``` Just be aware that this is the mathematical definition of compose - the data flows from right to left. If you want the data to flow left to right, use pipeline. The problem with compose is, that it makes it harder to debug, because we have to create a new function and add it to the compose/pipeline instead of just printing the result or setting a breakpoint ## Pointfree It means functions that never mention the data upon which they operate. First class functions, currying, and composition all play well together to create this style. ```javascript // not pointfree because we mention the data: word const snakeCase = word => word.toLowerCase().replace(/\s+/ig, '_'); ​ // pointfree const snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase); ``` The pointfree code can lead to writing of small functions, that just serve their concrete purpose. Be warned, however, pointfree is a double-edged sword and can sometimes obfuscate intention. Not all functional code is pointfree ## Functors * Container with map operation - something that can be mapped * A Functor is a type that implements map and obeys some laws - it comes from cathegory theory What do we gain from asking our container to apply functions for us? Well, abstraction of function application. When we map a function, we ask the container type to run it for us. ```js! class Container { constructor(x) { this.$value = x; } static of(x) { return new Container(x); } map(fn) { return Container.of(fn(this.$value)); } } Container.of('flamethrowers').map(s => s.toUpperCase()); // Container('FLAMETHROWERS') ``` This allows us to work with the values without ever leaving the container. Because it never leaves the container, we can always map over it. ### Maybe Maybe is a type of functor and is far more practical. It checks if the value is available before calling the map function. ```js! class Maybe { static of(x) { return new Maybe(x); } get isNothing() { return this.$value === null || this.$value === undefined; } map(fn) { return this.isNothing ? this : Maybe.of(fn(this.$value)); } } Maybe.of({ name: 'Boris' }).map(prop('age')).map(add(10)); // Nothing ``` ### Pure error handling - Either Throwing an exception isn't very pure. Using the Either type, we can respond with a message ```js! class Either { static of(x) { return new Right(x); } constructor(x) { this.$value = x; } } class Left extends Either { map(f) { return this; } } class Right extends Either { map(f) { return Either.of(f(this.$value)); } } ``` The Left subclass holds the unwanted value - it completely ignores mapping over it. The Right will happily map over the value. ```js! const printFuture = compose(map(console.log), map(future), getAge(moment())); printFuture({ birthDate: '2005-12-12' }); ``` If the date is valid, the future will be printed to console and result will be of the Right() type If the birthdate isn't valid, mapping over the Left type would do nothing and the function would return the Left type with corresponding error message. # Part2 ## Pointed interface * A pointed functor is a functor with an of method * `of` doesn't replace constructor, it is used to put values in a default minimal context ```js! IO.of('tetris').map(concat(' master')); // IO('tetris master') ``` ## Monads * when mapping over functors, we may end up with a nested functor * for example: there are three possible points of failure, so we can end up with something like this ```js! Maybe(Maybe(Maybe({name: 'Mulburry', number: 8402}))) ``` * it is not very nice to expect the caller to map three times over the functor to just access the value * this can happen quite often and monads will help us with it ### Monad * Monads are pointed functors that can flatten * that means they provide the `join` method, which removes the unnecessary wrappers * careful: the wrappers need to be of the same type ```js! Maybe.prototype.join = function join() { return this.isNothing() ? Maybe.of(null) : this.$value; }; const mmo = Maybe.of(Maybe.of('nunchucks')); // Maybe(Maybe('nunchucks')) mmo.join(); // Maybe('nunchucks') // firstAddressStreet :: User -> Maybe Street const firstAddressStreet = compose( join, map(safeProp('street')), join, map(safeHead), safeProp('addresses'), ); ``` * We can see there is a similar pattern - calling `map` and `join` immediately. * They can be composed into a single function called `chain` * aliases `>>=` or `flatMap` ```js! // chain const firstAddressStreet = compose( chain(safeProp('street')), chain(safeHead), safeProp('addresses'), ); ``` #### Assigning values using monads * because the `chain` effortlesly nests effects, we can capture `variable assignment` in a functional way ```ts! Maybe.of(3) .chain(three => Maybe.of(2).map(add(three))); // Maybe(5); ``` ## Applicative functors * we cannot call `add` and pass functors as it parameters, because it expects numbers * function `ap` allows us to apply function contents of one functor to another * "An applicative functor is a pointed functor with an ap method" * pointed means it has `of` method ```ts! Container.of(2).map(add).ap(Container.of(3)); // Container(5) ``` ### Lifting * `lift` functions allows us to `ap` as many times we want * the number at the end specifies the number of arguments get applied ```ts! const liftA2 = curry((g, f1, f2) => f1.map(g).ap(f2)); const liftA3 = curry((g, f1, f2, f3) => f1.map(g).ap(f2).ap(f3)); liftA2(add, Maybe.of(2), Maybe.of(3)); // Maybe(5) ``` ### Summary * applicatives are best used when we have multiple functor arguments * we can apply functions to these arguments * we can use the `lift` functions to make the applying more readable and nicer * we should prefer to use applicative functors instead of monads (if we don't need monadic functionality) ## Natural transformations * term from cathegory theory * they are functions upon functors themselves * we are familiar with type conversions * this time we are converting algebraic containers - eg. functors * using laws from cathegory theory we can convert functors between each other ```js! // ioToTask :: IO a -> Task () a const ioToTask = x => new Task((reject, resolve) => resolve(x.unsafePerform())); // maybeToTask :: Maybe a -> Task () a const maybeToTask = x => (x.isNothing ? Task.rejected() : Task.of(x.$value)); // arrayToMaybe :: [a] -> Maybe a const arrayToMaybe = x => Maybe.of(x[0]); ``` ## Traversals * let's start with an example ```js! // tldr :: FileName -> Task Error String const tldr = compose(map(firstSentence), readFile); map(tldr, ['file1', 'file2']); // [Task('Bill ran from the giraffe toward the dolphin.'), Task('Chocolate covered crickets were his favorite snack.')] ``` * the result of the example is array of Tasks - `[Task Error String]` * it would be nicer to have Task of array of strings -`Task Error [String]` * The Traversable interface consists of two glorious functions: **sequence** and **traverse** ### Sequence ```js! // sequence :: (Traversable t, Applicative f) => (a -> f a) -> t (f a) -> f (t a) const sequence = curry((of, x) => x.sequence(of)); ``` * examples ```js! sequence(List.of, Maybe.of(['the facts'])); // [Just('the facts')] sequence(Task.of, new Map({ a: Task.of(1), b: Task.of(2) })); // Task(Map({ a: 1, b: 2 })) sequence(IO.of, Either.of(IO.of('buckle my shoe'))); // IO(Right('buckle my shoe')) sequence(Either.of, [Either.of('wing')]); // Right(['wing']) sequence(Task.of, left('wing')); // Task(Left('wing')) ``` * the nested type becomes the outer type ### Traverse * traverse works similary to sequence but it takes an additional argument - function which will be applied ```js! // tldr:: FileName -> Task Error String const tldr = compose(map(firstSentence), readFile); traverse(Task.of, tldr, ['file1', 'file2']); // Task(['Bill ran from the giraffe toward the dolphin.', 'Chocolate covered crickets were his favorite snack.']) ``` ## The unwritten chapter - Monoids * the term monoid comes from group theory. * it is a pair - a type and an operation, satisfying a few conditions * the operation must combine two values of the type into a third value of the same type - eg. concat('a', 'b') => 'ab' * operation must be associative * existence of neutral element * we use monoids all the time - number addition, string concatenation... ### Reduce * everytime we have a monoid - we can use reduce to compute something ```js! const add = (a, b) => a + b; const addArray = arr => arr.reduce(add, 0); const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; addArray(numbers); // 55 ``` * because the operation is associative, we can cut the array into chunks, reduce those chunks and then combine the results of the chunks together - we need to preserve the order * we can use this asynchronously to paralellize the computation # Final summary of book * the book presents itself as complete, but the last chapter is referencing future chapter about monoids, but it is not there, although it wasn't updated in years * there are big jumps in difficulty in the chapters going from trivial examples to hard concepts, resulting in several rereads of a chapter * author uses some "fancy" language which makes the understanding hardes * "And now for our next trick, we'll look at traversals. We'll watch types soar over one another as if they were trapeze artists holding our value intact. We'll reorder effects like the trolleys in a tilt-a-whirl. When our containers get intertwined like the limbs of a contortionist, we can use this interface to straighten things out. We'll witness different effects with different orderings. Fetch me my pantaloons and slide whistle, let's get started." * the exercises are poor - poor documentation and task definition * rustlings are way better * although I wouldn't recommend the book, I still learned a lot from it about FP, it just wasn't that effective