mostly-adequate-guide icon indicating copy to clipboard operation
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

Open babakness opened this issue 7 years ago • 2 comments

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

babakness avatar Feb 22 '18 18:02 babakness

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.

DrBoolean avatar Feb 23 '18 00:02 DrBoolean

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

dmitriz avatar May 07 '18 13:05 dmitriz