Skip to content

Instantly share code, notes, and snippets.

@RyanCCollins
Last active December 21, 2016 04:34
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 RyanCCollins/b19ee057a2b28496ee3345535a4bd46b to your computer and use it in GitHub Desktop.
Save RyanCCollins/b19ee057a2b28496ee3345535a4bd46b to your computer and use it in GitHub Desktop.

Functional Programming in JS

Identity functor

const Identity = x => ({
  map: f => Identity(f(x)),
  fold: f => f(x),
  inspect: () => `Identity(${x})`
});

Usage

Example 1

const nextCharForNumberString = str =>
  Identity(str)
  .map(s => s.trim())
  .map(r => parseInt(r))
  .map(i => i + 1)
  .map(i => String.fromCharCode(i))
  .fold(c => c.toLowerCase());

console.log(nextCharForNumberString(' 64 ')); // "a"

Example 2

const moneyToFloat = str =>
  Identity(str.replace(/\$/g, ''))
  .map(r => parseFloat(r));

const percentToFloat = str =>
  Identity(str.replace(/\%/g, ''))
  .map(s => parseFloat(s))
  .map(r => r * 0.01);
 
const applyDiscount = (p, d) =>
  moneyToFloat(p)
  .map(c => percentToFloat(d)
    .fold(s =>
      c - c * s))
      .fold(f => `The total price is $${f}`)


console.log(
  applyDiscount('$50.50', '18%')
)

Either = Right || Left

const Right = x => ({
  map: f => Right(f(x)),
  fold: (f, g) => g(x),
  inspect: () => `Right(${x})`
})

const Left = x => ({
  map: f => Left(x),
  fold: (f, g) => f(x), 
  inspect: () => `Left(${x})`
})

Usage

Example 1

const findColor = name => {
  const found = ({ red: '#ff4444', blue: '#3b5998', yellow: '#fff68f' })[name];
  return found ? Right(found) : Left("🍆")
}

const result = findColor('green')
  .map(c => c.slice(1))
  .fold(e => `No color found ${e}`, 
    c => c.toUpperCase())

console.log(result); // "No color found 🍆"

const resultTwo = findColor('red')
  .map(c => c.slice(1))
  .fold(e => `No color found ${e}`, 
    c => c.toUpperCase())

console.log(resultTwo) //  "FF4444"

From Nullable

const fromNullable = x =>
  x != null ? Right(x) : Left(null);

const findColor = name =>
  fromNullable(({ red: '#ff4444', blue: '#3b5998', yellow: '#fff68f' })[name]);

const result = findColor('ref')
  .map(c => c.slice(1))
  .fold(e => `No color found`, 
    c => c.toUpperCase())

console.log(result);

Semi Groups

const Sum = x =>
({
  x,
  concat: ({ x: y }) =>
    Sum(x + y),
  inspect: () => `Sum(${x})`
})

const res = Sum(1).concat(Sum(2))

const All = x => 
({
  x,
  concat: ({ x: y }) =>
    All(x && y),
  inspect: () => `All(${x})` 
})

const resAll = All(true).concat(All(true));

console.log(resAll.inspect())

const First = x => 
({
  x,
  concat: _ =>
    First(x),
  inspect: () => `First(${x})` 
})

const resFirst = First('Foo').concat(First('Bar')).inspect();
console.log(
  resFirst
)

More Examples

Concat maps using semi-groups

import React from 'react';
import { Map } from 'immutable-ext';
import styled from 'styled-components';

const Sum = x =>
({
  x,
  concat: ({ x: y }) =>
    Sum(x + y),
  inspect: () => `Sum(${x})`
})

const All = x => 
({
  x,
  concat: ({ x: y }) =>
    All(x && y),
  inspect: () => `All(${x})` 
})

const First = x => 
({
  x,
  concat: _ =>
    First(x),
  inspect: () => `First(${x})` 
})

const acct1 = Map({
  name: First('Nico'),
  isPaid: All(true),
  points: Sum(10)
});

const acct2 = Map({
  name: First('Nico'),
  isPaid: All(false),
  points: Sum(2)
});

const res = acct1.concat(acct2);

const Container = styled.div`
  width: 100vw;
  height: 100vh;
  display: flex;
  alignItems: center;
  justifyContent: center;
  flexDirection: row;
`;

const Code = styled.code`
  background: #f5f5f5;
  width: 200px;
  height: 200px;
  padding: 30px;
`;

function MergedAccounts () {
  return (
    <Container>
      <Code>
       <pre>
         {JSON.stringify(res.toJS(), null, 2)}
       </pre>
     </Code>
    </Container>
  );
}

export default MergedAccounts;

Monoids

To ensure that we can reduce on empty objects, a Monoid must have a .empty() method.

export const Sum = x => ({
  x,
  concat: ({ x: y }) =>
    Sum(x + y),
  inspect: () => `Sum(${x})`
});

// Makes it a Monoid
Sum.empty = () => Sum(0);

export const All = x => ({
  x,
  concat: ({ x: y }) =>
    All(x && y),
  inspect: () => `All(${x})`
});

// Makes all a monoid
All.empty = () => All(true);
  Sum(0).reduce((acc, x) => acc(x))

Fold Map

Common to first map to a monoid, then fold

const res = Map({ brian: 2, sara: 4, luke: 5 })
  .map(Sum.empty())
  .fold(Sum)

becomes

const res = Map({ brian: 2, sara: 4, luke: 5 })
  .foldMap(Sum, Sum.empty())

Applicative Functors

const Id = x => ({
  chain: f => f(x),
  ap: b => b.map(x),
  map: f => Id(f(x)),
  fold: f => f(x),
  inspect: () => `Id(${x})`
});

const add = x => y => x + y;

const res1 = Id(add).ap(Id(3)).ap(Id(3))

// Laws for applicative functors
// F(x).map(f) == F(f).ap(F(x))
const liftA2 = (f, fx, fy) =>
  fx.map(f).ap(fy);
  
const res = liftA2(add, Id(2), Id(4)).fold(x => x);
console.log(res);

Isomorphism

const Iso = (to, from) => ({
  to,
  from
});

const chars = Iso(s => s.split(''), c => c.join(''))
// const res = chars.from(chars.to('hello world'))

const truncate = str =>
  chars.from(chars.to(str).slice(0, str.length - 3).concat('...'))

const res = truncate('Hello world this is my story and I am sticking to it. So it all started')

console.log(res);

Example 2

const Right = x => ({
  map: f => Right(f(x)),
  fold: (f, g) => g(x),
  inspect: () => `Right(${x})`
})

const Left = x => ({
  map: f => Left(x),
  fold: (f, g) => f(x), 
  inspect: () => `Left(${x})`
})

const Iso = (to, from) => ({
  to,
  from
});

const singleton = Iso(e => e.fold(() => [], x => [x]),
                      ([x]) => x ? Right(x) : Left())

const filterEither = (e, pred) =>
  singleton.from(singleton.to(e).filter(pred));
  
const res = filterEither(Right('hello'), x => x.match(/h/ig))
  .map(x => x.toUpperCase());
  
console.log(res.inspect());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment