See WIP implementation HERE
In Haskell we can define Monoid instances like this:
instance Monoid b => Monoid (Identity b) where
mempty = mempty
mappend (Identity a) (Identity b) = Identity (a `mappend` b)
instance (Monoid a, Monoid b) => Monoid (Pair a b) where
mempty = Pair mempty mempty
mappend (Pair u v) (Pair w x) = Pair (u `mappend` w) (v `mappend` x)
instance Monoid b => Monoid (a -> b) where
mempty _ = mempty
mappend f g x = f x `mappend` g x
But we can't do same in JavaScript as we don't have type information upfront
Identity.empty = ??
Pair.empty = ??
Function.empty = ??
One thing we can do is have ListIdentity for example:
const makeIdentity = M => {
function Identity ...
Identity.empty = Identity(M.empty)
return Identity
}
ListIdentity = makeIdentity(List)
StringIdentity = makeIdentity(String)
But it would be a clanky to implement map
we might go from Identity of List to Identity of String:
const makeIdentity = M => {
...
Identity.prototype.map = function(f) {
const nextVal = f(this.value)
// we might memoize makeIdentity to not create multiple ListIdentity
// but i's not nice we can do better
return makeIdentity(nextVal.constructor)(nextVal)
}
...
}
So what if in cases when we don't know what where to find desired empty
value we had some special universal value. let's create one and see how it could be useful with Identity:
// empty :: (Monoid m) => m
// this is a special Monoid value but we don't know for which type.
const isEmpty = m => m['@functional/empty'] === true
const empty = {
'@functional/empty': true,
constructor: { empty },
concat: a => a
}
// [1] - if value in `this` is that empty value than
// concating to it is same as just returning `b`
// [2] - if value in `b` is that `empty` value or if `b` itself is the
// `empty` than concating to it is same as just returning `this`
// [3] - if non of them are empty then we can just concat inner values
Identity.empty = Identity(empty)
Identity.prototype.concat = function(b) {
if (isEmpty(this.value)) { // [1]
return b
} else if (isEmpty(b) || isEmpty(b.value)) { // [2]
return this
} else { // [3]
return Identity(this.value.concat(b.value))
}
}
empty.concat(Identity([])) // = Identity([])
Identity.empty.concat(Identity([])) // = Identity([])
Identity([]).concat(empty) // = Identity([])
Identity([]).concat(Identity.empty) // = Identity([])
Now let's do it for Pair
:
Pair.empty = Pair(empty, empty)
Pair.prototype.concat = function(b) {
if (isEmpty(this._1) || isEmpty(this._2)) {
return b
} else if (isEmpty(b) || isEmpty(b._1) || isEmpty(b._2)) {
return this
} else {
return Pair(this._1.concat(b._1), this._2.concat(b._2))
}
}
empty.concat(Pair("", [])) // = Pair("", [])
Pair.empty.concat(Pair("", [])) // = Pair("", [])
Pair("", []).concat(empty) // = Pair("", [])
Pair("", []).concat(Pair.empty) // = Pair("", [])
Nice! And here is for Function
:
Function.empty = (_) => empty
Function.prototype.concat = function(g) {
if (isEmpty(g)) {
return this
}
const f = this
return (x) => {
const a = this(x)
const b = g(x)
if (isEmpty(a)) {
return b
} else if (isEmpty(b)) {
return a
} else {
return a.concat(b)
}
}
}
empty.concat((i) => [i+1, i-1]) // = ((i) => [i+1, i-1])
Function.empty.concat((i) => [i+1, i-1]) // = ((i) => [i+1, i-1])
;((i) => [i+1, i-1]).concat(empty) // = ((i) => [i+1, i-1])
;((i) => [i+1, i-1]).concat(Function.empty) // = ((i) => [i+1, i-1])
Lets's go even crazier and implement Monoid for task which executes task in parallel and concat's it's results
Task.empty = Task((rej, res) => void res(empty))
Task.prototype.concat = function(g) {
if (isEmpty(g)) {
return this
}
return Task.parallel([this, g]).map(([a, b]) => {
if (isEmpty(a)) {
return b
} else if (isEmpty(b)) {
return a
} else {
return a.concat(b)
}
})
}
This technique could be used for universal of
method which just takes a value and does not need constructor, but returns a "partial" or "phantom" value like this:
const isOf = a => a['@functional/of'] === true
const of = (a) => ({
'@functional/of': true,
value: a,
constructor: {
of,
chainRec: (f, i) => {
const step = f(
value => ({ done:false, value }),
value => ({ done:true, value }),
i
)
return step.chain(({ value, done }) =>
done ? step.constructor.of(value) : step.constructor.chainRec(f, value)
)
}
},
map: (f) => of(f(a)),
ap: (f) => isOf(f) ? of(f.value(a)) : f.constructor.of(a).ap(f),
concat: (b) => {
if (isOf(b)) {
return of(a.concat(b.value))
} else if (isEmpty(b)) {
return of(a)
} else {
return b.constructor.of(a).concat(b),
}
}
chain: (f) => f(a),
})
of('/posts').chain(url => of(url).chain(of)) // = of('/posts')
of('/posts').chain(of).chain(of) // = of('/posts')
of('/posts').chain(url => Http.get(url)) // = Http.get('/posts')
of('/posts').ap(url => Http.get(url)) // = Http.get('/posts')
of('/posts').ap(of(a => a)) // = of('/posts')
of(empty).concat(of(empty)) // = of(empty)
empty.concat(of(empty)) // = of(empty)
of(empty).concat(empty) // = of(empty)
Identity.prototype.ap = function(f) {
if (isOf(f) {
return this.map(f.value)
} else {
Identity(f.value(this.value))
}
}
of(10) // [1]
.ap(of(a => a)) // [2]
.chain(a => of(a)) // [3]
.chain(a => Http.get(a)) // [4]
// [1,2,3] - We don't know in which type we are wrapping values, we have `Identity`
// like default implementation for every interface it could posibly become
// [4]- now as Http.get returns Task, value would be of that type.
// we don't know and don't need to know the type upfront.
// 1,2,3 could be in seperate function without knowing which
// type we would use next.
We could have this isEmpty/empty/of/isOf
in libs like ramda or maybe as separate module or in fantasy-land.
Implementations might not be 100% correct, I have not checked them but it gives a general idea. In next couple days I would write tests for it and check if all works correctly.
Inspiration: Monads in Dynamically-Typed Languages
Discussions: