mostly-adequate-guide
mostly-adequate-guide copied to clipboard
Ch 13 - "a monoid in the category of Endofunctors also known as a Monad" and First as a Monoid
There are so many talks that and articles that open with the "A Monad is just a Monoid in the category of Endofunctors, What's the Problem?" that it has become a meme.
Right away I think: so can a Monad not be Monoid and what if I have a functor with .map + .join and not .concat and .empty? And if the concat and/or empty can break is it still useful?
BTW, Doesn't this modification to First make it promotable into a Monoid?
const First = x => ({
x,
concat: other => First(x),
empty: () => First(x),
map: () => First(x),
join: () => x,
chain: () => x,
})
First.of = (x) => First(x)
First.empty = () => First
First.concat = (x) => First(x)
// Monoid?
First.empty().concat(5).map( x => x+1 ).empty().concat(10).join() // 5
Couldn't this First
Functor here could be also called the Constant
functor, similar in spirit to const constant = a => b => a
aka K or Kestrel--once given something that is... yeah I'm just talking out loud here ;-)
Maybe its even a better Monoid than Identity, since concat
and empty
on identity can break depending on its content. First here might be pretty much bulletproof compared to Identity/Right/Just with .concat or .empty
Just.of(10).concat(1)
Does it make more sense to throw an error or to return a Nothing
? Does
Just.of(foo: 'bar'}).empty()
Return a Nothing
or does some Object.prototype.empty
get called and we get Just({})
? Because the latter seems more like
Just.of({foo: 'bar'}).map( obj => obj.empty())
In Chapter 13 we have
Identity.of(Sum(4)).concat(Identity.of(Sum(1))) // Identity(Sum(5)) Identity.of(4).concat(Identity.of(1)) // TypeError: this.__value.concat is not a function
Which is Identity.of(Sum(4)).map( obj => obj.empty())
Defined as
const Identity = x => ({
concat: y => Identity.map( x.concat(y) )
})
But in these example
const Any = x => ({ x, concat: other => Any(x || other.x) })
const All = x => ({ x, concat: other => All(x && other.x) })
Any(false).concat(Any(true)) // Any(true)
Any(false).concat(Any(false)) // Any(false)
All(false).concat(All(true)) // All(false)
All(true).concat(All(true)) // All(true)
[1,2].concat([3,4]) // [1,2,3,4]
"miracle grow".concat("n") // miracle grown"
Map({day: 'night'}).concat(Map({white: 'nikes'})) // Map({day: 'night', white: 'nikes'})
The concat isn't working directly with the contents of the container itself. This seems rationally inconsistent to my naive look at these things
An explanation of "a monoid in the category of Endofunctors also known as a Monad" is very much sought out and especially in a fluid / lucid way that ties back into the thought process throughout the book.
Anyway, I hope my confusion is at least thought provoking
Hey @babakness
So to address the questions here:
Right away I think: so can a Monad not be Monoid and what if I have a functor with .map + .join and not .concat and .empty? And if the concat and/or empty can break is it still useful?
That is actually related to your observation about:
The concat isn't working directly with the contents of the container itself. This seems rationally inconsistent to my naive look at these things
Since Functors/Monads act as containers, they act on the values they hold. Since there are many possible monoid instances, the accepted convention is that they will delegate to what they hold. e.g Task('hello').concat(Task('world')) = Task('helloworld')
. This is helpful for cascading effects while concating
. Functors will act as containers whereas the Sum/Any/All/Max/Min/etc
types exist solely as monoids so the behavior is defined by them, not what they are holding. In short, the "containerness" is why they seem different, but they all combine in a reasonable way and, indeed, could combine in different ways.
BTW, Doesn't this modification to First make it promotable into a Monoid?
It certainly does! You've discovered a trick many people use. However, it leads to a bit of weirdness since one expects a "special value" available even if we have nothing else: for instance think of returning Nothing
instead of 0
for sum([])
.
Also, yep, that's Const
alright.
BTW, Doesn't this modification to First make it promotable into a Monoid?
No, the "First
" is a Semigroup that cannot be promoted to Monoid, because you cannot define the empty()
element that is both right and left identity:
concat(empty(), something) is NOT something