Skip to content

Instantly share code, notes, and snippets.

@raynerpupo
Last active June 6, 2019 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raynerpupo/0ca72d58f38a3ce7182b5a5a7e479708 to your computer and use it in GitHub Desktop.
Save raynerpupo/0ca72d58f38a3ce7182b5a5a7e479708 to your computer and use it in GitHub Desktop.

Intro

Discipline is the future

https://www.youtube.com/watch?v=ecIWPzGEbFc blah Uncle Bob Martin conference about the Future of programming, can reduce the whole conference in one word "discipline" FP paradigm is even older than others mainstream like OOP. It's taking a rise lately given most of the current languages/ecosystems can eventually get an approximate result, yet the time and effort to build and refactor could be very different. FP isn't a silver bullet but it gives some hints on how programs could be escalated with less effort.

Purity

A pure function responds to 2 laws: 1- Referential transparency: given a set of arguments the function should always return the same value This is a very important thing, we should try to create as long as possible functions that are predictable this eases testing and give us the confidence over the outputs just like the mathematics formulas work.

const getDouble = (x: number) => x * 2;

getDouble(4); //this is always returns 8

2- No side effects: The function evaluation should not mutate any of the external state.

interface Human {
  name: string;
  age: number;
}

const friend: Human = {
  name: 'Rambo',
  age: 45
};

const celebrateBirthday = () => {
  friend.age = friend.age + 1;
  console.log(`Happy birthday number ${friend.age}`)
  return friend;
}

const celebrateBirthdayBetter = (person) => {
  const olderFriend = {
    ...person,
    age: person.age + 1
  };
  console.log(`Happy birthday number ${olderFriend.age}`);
  return olderFriend;
}

Of course we need most of the time a state or something that could change over time but the thing here is to try to isolate the uncertainity. This also stands when we need something from an external source say an API call, an async calculation, anything we need from the "outside" world since we know the world is full of uncertainity :)

Why it's important

What do we get by isolating the effects from the predictable: The testing is way more easier. The possible refactoring is improved since we can target the functions that are responsible of mutation or talk to the outside without touching the pure ones.

Immutability

Immutability is often a requirement for purity, this term states that something will hold its initial value accross its lifespan. With this approach we can be sure that every time we ask for something the same value is evaluated.

Importance

Once again the Refactoring process could be improved by enforcing immutability. Since the values are "frozen" you don't need to keep track whether a previous sentence changed the value of our variable leading to unexpected results. Immutability often enforces the usage of meaningful names and improves the readability of the code

Function composition

This is the technique to stitch together small functions with a sole purpose to fulfill a biger need.

  • Small functions that do only one thing are the beggining of the SOLID principle, literally speaking :)
  • Small functions are often reusable to build up other bigger abstractions
  • Testing small predictable functions could be considered as a watercooler time :), no seriously it's quite easy
  • The function composition enables a sort of piping of operations
  • Helps to identify whether a function became a monster that need to be splitted into smaller abstractions (if a function is not composable by itself probably needs to be decoupled)

Point free style

Also called Tacit programming is refered to a style where the arguments of the function are not explicit, this is particulary useful when creating a function through the composition with others.

const compose = (...fns: Function[]) => {
  return (args: any) => {
    return fns.reduceRight((arg, fn) => fn(arg), args)
  }
};

const length_ = (str: string): number => str.length;
const toString_ = (n: number): string => n.toString();
const plusOne = (n: number): number => n + 1;

const incrementedLenght = compose(length_, toString_, plusOne);

console.log(incrementedLenght(345));//3
console.log(incrementedLenght(3456));//4
console.log(incrementedLenght(9999));//5

First order functions

A first order function is a function that can take or return other functions. Once again this is a concept that favors generalization and abstraction over rigidity and specialization. First order functions enables dependency injection in languajes like JavaScript, we will see more about this topic later on. It also allows to create behaviour factories that returns functions for a given command or just simple let us to decorate some function evaluation with additional processing.

type Action = 'plus1' | 'duplicate' | 'square';

const createAction =
  (action: Action): (n: number) => number => {
    switch (action) {
      case 'plus1':
        return n => n + 1;
      case 'duplicate':
        return n => n * 2;
      case 'square':
        return n => n * n;
      default:
        return n => n;
    }
  }

const actions = [
  createAction('plus1'),
  createAction('duplicate'),
  createAction('square')
];

const fiveTrans = actions.map(fn => fn(5));
//[6, 10, 25]

Todays JS code is full of first order functions either by libraries usage, as a convenience to handle asynchronous code (callbacks) or just as a code execution deferral.

Wholemeal programming

This technique allows us to think about one solution or transformation over a sequence instead of sequencing the transformations for each element of the sequence. It solves the indexitis problem by getting rid of the index-based looping, indexes are proven to be error prone. More on this later with a real life example from Sebas.

Recursion

You might say "what's the point of immutability when you need a state", well you still can have a "changing" state with immutability 👽. This can be accomplished by using recursion. Let's say we're writing a really boring game where we don't have any kind of interaction, just at the moment we have a state representing the current steps count and the expected output is a set of action logs for each step up to 50:

interface GameState {
  steps: number;
}

type Actions = string[];

There's also an alias for string[] so we can use Actions instead.

Now we need a function that creates the actions for every step.

const playImperative = ({ steps }: GameState) => {
  let actions: Actions = [];
  let currentStep = steps;
  for (currentStep; currentStep < 50; currentStep++) {
    const newAction = `Moving one step forward from ${currentStep}.`;
    actions.push(newAction)
  }
  actions.push('Finished');
  return actions;
}

playImperative({ steps: 0 });

This is the procedural approach where we rely on both actions and currentStep to hold our state across the whole execution. This way of handling our problem has some moving parts despite the fact that our current business logic is ridiculously simple. Moving parts are always prone to bugs and we need to pay an extra attention to keep them clean.

But how can I handle this very problem but keeping all stuff immutable? Is that even possible? Or recommended?

const playRecursive = ({ steps }: GameState): Actions => {
  if (steps >= 50) {
    return ['Finished'];
  }
  const newAction = `Moving one step forward from ${steps}.`;

  return [newAction, ...play({ steps: steps + 1 })];
}

playRecursive({ steps: 0 });

This version avoids immutability at all cost and could be considered easier to refactor since we're getting rid of the whole iteration thing in favor of a function that handles one state at a time.

But this function has a important flaw, each recursive call creates a frame into the stack and it will eventually create an overflow. So, how can we still use a recursive function and avoid this problem?

const play = (state: GameState): Actions => {
  const play_ = ({ steps }: GameState, acc: Actions = []): Actions => {
    if (steps >= 50) {
      return [...acc, 'Finished'];
    }
    const newAction = `Moving one step forward from ${steps}.`;
    return play_({ steps: steps + 1 }, [...acc, newAction]);
  }
  return play_(state);
}

This technique is called Tail Recursion and some languages have an Optimize way to create just one frame on each call, EcmaScript 6 has it on its proposals but just a few engines have comply so far. This kind of optimization can only be applied on strict mode "use strict"; and the recursive call need to be the last thing we call in the function. Keep in mind if your function doesn't returns the recursive call it will implicitly call return undefined; at the end, hence the optimization condition isn't meet either.

Currying and partial application

A curried function takes only one argument and returns another function. You can convert any multi-parameter function into a curried version, this curried version need that each parameter for the original one is supplied through the invocation so given the following function example:

const sumNormal = (a, b, c, d) => a + b + c + d;

would become:

const sumCurried = a => b => c => d => a + b + c + d;

then the call difference is:

sumNormal(1, 2, 3, 4)//10

sumCurried(1)(2)(3)(4)//10

You may wonder why this is useful at all, well by deferring the execution you gain a lot of flexibility, you can apply just the amount of paramaters it makes sence at a certain point for a given business logic and let the other for a future moment where they come into context. Considering the latest sumCurried example you can do the following:

const plusFive = sumCurried(5);
...
plusFive(2)(3)(4)//14

At the time we created the plusFive function we just consider that we need to sum with 5, the other operands are unknown or just irrelevant so far. This technique is called partial application, we can create a new function from a curried one where some of the first parameters were already applied. There're some languages like Haskell were the currying is automatically made, even though you might see what it seems to be a function that receives several arguments, under the hood it converts that function to its curried version so you can use it like so. In JavaScript we don't have automatic currying, yet there're some libraries that help us like RamdaJS and a fork from Lodash called LodashFP. Also we can implement our "nahive" version of curry, a function that takes as an argument a uncurried function and converts it to its curried version:

const curry = (fn: Function): Function => {
  if (fn.length < 2) {
    return fn;
  }
  return (...p) => curry(fn.bind(this, ...p))
}

Using this function we can convert our previous sumNormal function and partially apply our arguments as we please:

const curriedSum = curry(sumNormal);

// One by one
curriedSum(1)(2)(3)(4) //10

// Partially applied
curriedSum(1, 2)(3)(4) //10

curriedSum(1, 2, 3)(4) // 10

FP libs

RamdaJS

This library have hundred of utility functions to make JS programmers life easier, the most interesting part is that all those functions are already curried so you can do something like:

const allSquared = R.map(n => n * n);

allSquared([1, 2, 3, 4])//[2, 4, 6, 8]

As you can see the RamdaJS version of the map function does pretty much the same as the built in one but

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment