M*****

Technical, Functional programming

title: M***** title_hover: Monads created_at: 2020-12-28T14:52:16.823Z updated_at: 2020-12-28T14:52:16.823Z tldr: The not so dreadful m-word in programming is_published: 1 star: true category: Technical, Functional programming share_type: share

Forgetting the math behind what monad is, let's take a bird's eye look at how it works.

Monads are simple.

Monads are software design patterns that allow a user to chain operations, while the monad changes secret work behind the scenes.

Let's travel this land from no monad pattern to a monad pattern

Let's say we have a banking requirement

function debitUserAccount(currentSaving: number, amount: number): number {
  const newSavings = currentSaving - amount;
  return newSavings;
}

function creditUserAccount(currentSaving: number, amount: number): number {
  const newSavings = currentSaving + amount;
  return newSavings;
}

// we call it by
debitUserAccount(100, 1);

// we can chain it like
debitUserAccount(creditUserAccount(99, 1), 1);

now say we need to add logs every time a transaction is made

interface Savings {
  current: number;
  logs: string[];
}

function debitUserAccount(currentSaving: number, amount: number): Savings {
  const newSavings = currentSaving - amount;
  return {
    current: newSavings,
    logs: [`${amount} was debited, new savings: ${newSavings}`],
  };
}

function creditUserAccount(currentSaving: number, amount: number): Savings {
  const newSavings = currentSaving + amount;
  return {
    current: newSavings,
    logs: [`${amount} was credited, new savings: ${newSavings}`],
  };
}

// now we cant chain these functions
debitUserAccount(creditUserAccount(99, 1), 1);

debitUserAccount function needs a number but creditUserAccount returns a Savings interface.

function debitUserAccount(savings: Savings, amount: number): Savings {
  const newSavings = savings.current - amount;
  return {
    current: newSavings,
    logs: savings.logs.push([
      `${amount} was debited, new savings: ${newSavings}`,
    ]),
  };
}

function creditUserAccount(savings: Savings, amount: number): Savings {
  const newSavings = savings.current + amount;
  return {
    current: newSavings,
    logs: savings.logs.push([
      `${amount} was credited, new savings: ${newSavings}`,
    ]),
  };
}

// to get to a savings type we need to create a savings wrapper
// it just wraps the plain amount to `Savings` type
function createSavings(amount: number): Savings {
  return {
    current: amount,
    logs: [],
  };
}

// so now we can chain this in any order
debitUserAccount(creditUserAccount(createSavings(99), 1), 1);

In the above functions we just created a wrapper functions for savings and created 2 operations functions which can be called on savings in any order which returns a savings type

There seems to be a duplicated logic of addigns logs, lets refactor this so that we can run the operation logic for the savings .

function applyTransactions(
  savings: Savings,
  transaction: (amount: number) => () => Savings,
): Savings {
  const newTransaction = transaction(savings.current);

  return {
    current: newTransaction.current,
    logs: [...savings.logs, ...newTransaction.logs],
  };
}

// and we rewrite debit and credit function as
function debitUserAccount(amount: number): Savings {
  return (savings: numbers) => {
    return {
      current: savings - amount,
      logs: [`${amount} was debited, new savings: ${newSavings}`],
    };
  };
}

function creditUserAccount(amount: number): Savings {
  return (savings: number) => {
    return {
      current: savings + amount,
      logs: [`${amount} was credited, new savings: ${newSavings}`],
    };
  };
}

oh nice, now debit and credit don't need to do append logs anymore, they are independent functions on their own. And they don't need a savings type anymore they just need the amount to do the transaction.

Now we can do indipendent transactions in any order we want to

const savings = createSavings(99);
const d1 = applyTransactions(savings, debitUserAccount(1));
const c1 = applyTransactions(d1, creditUserAccount(1));
...

// we can also add new transactions without log concatination logic
const i1 = applyTransactions(d1, creditInterest(0.7));

well this is a Monad, Tada 🎉. Monads at thier core are helpful design patterns. We just built one. They allow to chain operations, managing busy work behind the scene.

All monads have 3 components

> Wrapper type: Savings

> Wrapper function: createSavings
// they are help take a non monadic thing and wrap to a monadic world
// like a constructor

> Runner: applyTransactions
// they take objects in the monadic world and apply transformation on them

well this is just an introduction to how monad patterns can be incredibly useful in designing applications. There are may more of wonders in mondads like options, lists, compose, promise ...

I hope this made monads little more approchable...

Further reading