Ok let me give you an example of a monad! The Maybe monad. In this case a monadified value either has a normal value, or it has a null. Ok, so for the operations. "return" should monadify a value. In this case it does this by creating a Maybe that has that value (Maybes that have null can not be created with return. They must be created in some other way.). The bind operation creates a new function that takes a Maybe value from one that doesn't. If the Maybe has a normal value then the function is run like normal. But if the Maybe has a null, the function is not run. Instead null is emitted directly.
Why is this a good thing? It means you can chain together many failure prone operations, and you only have to check for null at the end, not in the middle. So it works a little bit like exceptions.
Let's verify the laws:
1. We have some value, and some function that could take that value, and either emit a result, or a null. If we monadify our value, we get a Maybe that has that value. If we bind our function, and then run it with our Maybe, what happens? Well we know our Maybe is not null, because we just put in a totally legit value. So because of how we defined our bind, the function will run just like normal.
2. Ok so lets bind "return" and see what happens. If the input is a maybe that has null, then we never run "return" but just emit null. Consistent with identity!
If the input is a maybe with a normal value, then we run "return" with that value. Return creates a maybe that has that value. So we get out what we put in, it works!
3. There are a number of cases we have to go through but let's do them one by one.
First construction:
a) x is null. f_ skips f and just emits null. g_ skips g and just emits null.
b) x is non-null. first we put x in f_. Since x is not null we just apply f to the normal value. Now f can either emit a normal value or null.
b1) f emits null. g_ is skipped result is null.
b2) If result is not null, g is run with the normal result, and result of overall computation is g(f(normal argument))
Second construction:
a) x is null. g_f_ skips g_f and just emits null. Same result as before
b) x is not null. g_f is run with value in x. To run g_f we first run f. Two cases. f emits normal value or null.
b1) f emits null. g_ of null is null. So if value in x causes f to emit null we have null just as before.
b2) f does not emit null. g_ just runs g with normal result. So if value in x does not cause f to emit null, result of computation is g(f(normal argument))
So both constructions work the same, they continue running until problem arises.
Why is this a good thing? It means you can chain together many failure prone operations, and you only have to check for null at the end, not in the middle. So it works a little bit like exceptions.
Let's verify the laws:
1. We have some value, and some function that could take that value, and either emit a result, or a null. If we monadify our value, we get a Maybe that has that value. If we bind our function, and then run it with our Maybe, what happens? Well we know our Maybe is not null, because we just put in a totally legit value. So because of how we defined our bind, the function will run just like normal.
2. Ok so lets bind "return" and see what happens. If the input is a maybe that has null, then we never run "return" but just emit null. Consistent with identity! If the input is a maybe with a normal value, then we run "return" with that value. Return creates a maybe that has that value. So we get out what we put in, it works!
3. There are a number of cases we have to go through but let's do them one by one.
First construction: a) x is null. f_ skips f and just emits null. g_ skips g and just emits null. b) x is non-null. first we put x in f_. Since x is not null we just apply f to the normal value. Now f can either emit a normal value or null. b1) f emits null. g_ is skipped result is null. b2) If result is not null, g is run with the normal result, and result of overall computation is g(f(normal argument))
Second construction: a) x is null. g_f_ skips g_f and just emits null. Same result as before b) x is not null. g_f is run with value in x. To run g_f we first run f. Two cases. f emits normal value or null. b1) f emits null. g_ of null is null. So if value in x causes f to emit null we have null just as before. b2) f does not emit null. g_ just runs g with normal result. So if value in x does not cause f to emit null, result of computation is g(f(normal argument))
So both constructions work the same, they continue running until problem arises.