const Identity = x => ({
map: f => Identity(f(x)),
fold: f => f(x),
inspect: () => `Identity(${x})`
});
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"
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%')
)
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 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"
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);
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
)
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;
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))
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())
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);
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);
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());