Haskell for Imperative Programmers #17 - Monads
303 segments
today we are going to close the book on
the theoretical aspects of Haskell
because today we are going to talk about
monads now monads for a lot of people
are this magical thing that nobody can
explain right and nobody really
understands but that really isn't the
case as we can see here when we look at
the info for monads and if we look at
the common saying what the minimal
function is that we need to have for a
Monat we see that there is only one
function that we actually need in order
to have a monad so this means that if we
understand this one function we should
be able to understand monads but
actually we are going to look at three
functions here we are going to look at
return the greater greater and the
greater greater equals but let's make
another observation right now maybe and
IO the types and the IO actions that we
have seen before are monads so this is
really interesting because we have
worked with monads before we just didn't
know okay so let's look at the very
important thing this greater greater
equals operator it is also called bind
and it works like this we get a monad of
type a and a function a to monnet B and
then we get a monad B so this is
interesting because as we can see here
we get the internal type of the monad so
in the case of a maybe read a just one
has the internal value one and an i/o
action for example also has some
internal value getline has some internal
string so this bind operator seems to be
able to extract that value the question
is how does it do it well let's look at
two examples with the maybes a just one
is just what we expect we have the
internal value one and then we have this
anonymous function here that
gets this one as its ex argument and
then just puts it back in to adjust why
not
but then the interesting thing is a
nothing with this bind operator gets us
a nothing now
this seems to be weird because why is
that the case shouldn't we get a value
well no because a nothing doesn't have
an internal value and of course it
depends on how you define the spined
operator but since a nothing encapsulate
some error state very often you know it
encapsulates that you have nothing in
your hands then it shouldn't return
anything but a nothing okay so now that
we know that why not write a function
with this which we will call maybe add
which takes a maybe of X and a value Y
and then adds them together the
important thing is that we still have to
return a maybe after this right
otherwise we are not don't have a sound
type because the bind operator has to
return a monad so we just return this
just of the sum of those two values so
as we can see here if we do some adding
with a nothing we actually get a nothing
which is what we want because we cannot
add to nothing so the error if some
error happened is propagated and if we
have a just of one for example and we
add a 1 to it we get a just 2 so this
works just as expected from this we can
build something even crazier where we do
this with two maybes now we have 2
maybes that we use the bind operator on
in order to get the internal values and
then we sum them together and throw them
into the just constructor again even
though I don't have a example here if
the second argument is nothing we get
nothing if the first argument is nothing
we get nothing we only get a just of any
value if the two values we throw in here
are just
okay so now we've seen that we can do it
like this but remember there was this
one function in the monads which was
called return and return should take a
value and then return the monad of that
value so maybe also has to have a return
of course because otherwise maybe
wouldn't be a monad so we can use return
here and that is true but now let's look
at something interesting because the
type actually changes so the most
general type that we have now is a monad
of B's - a monad of B's - a monad of
beasts now this still works with maybes
right because instead of a monad you can
write a maybe because maybe it's a monad
but now you could also use this on IO
ends and you can use this on any monad
that has the internal internal type that
is in this num type class so you could
use anything basically you could use a
reader you could use some some network
sockets if you get a number out of the
monad you can use it with this maybe add
function and now we can think about
renaming this function leading up to the
best joke of this whole series we will
call this function monad with two DS
because it's a magnetic ad right that
was worth it okay so let's look at this
function again and when looking at this
function again we maybe see that okay if
we want to now use even more monads if
we want to have even more arguments this
syntax becomes really convoluted and a
bit ugly because we are using this
operator the spined operator all the
time with an anonymous function
definition this is not really the way to
go is it and no it isn't which is why
there is an alternative syntax that we
have already seen and it's the du
notation because if you have a bar
where you say well the Monad em gets
bound to this anonymous function with
the argument X this is the same in the
dew notation as saying well X with this
left arrow M which says nothing but well
take the value of the internal value of
M and put it into X and then do
something else but again remember if
there is a fault he state in our monad
so for example if the maybe is nothing
or even IO has some internal exception
then we actually because the dew
notation is nothing but the bind
operator then we actually propagate this
error through so we don't have to think
about errors in this case we always
think about getting a value but we can
be sure that if there's an error if for
example and nothing is returned then
this is just propagated through so this
is great because this lets us build pure
functions that can still handle errors
and exceptions that happen on the side
right and using do notation we can
actually rewrite this a monad function
and it looks like this and let's just go
through it we have the Monad x'
x and y the monads MX and my of course
and we get those values with x left
arrow m x and y left arrow and y and
then we return the sum of those two
great so maybe let's maybe look at
something interesting the actual
implementation of a monad for them maybe
because it's actually really easy so we
have this bind operator here and we do a
matching I don't think I have mentioned
this in this series but this is also a
way of doing pattern matching and here
we match the M to nothing and in this
case we just return nothing and if we
have a just of X we apply the function
to it right this is exactly what binds
should do and a return
of any value is just a wealth just of
that value great so this is how a monad
can be instantiated in and if you have
your own type for example for a random
number generator or for something that
has to hold a state you can use it just
like this okay
so we've talked about the most important
thing the bind let's talk about this one
fail what does that do well fail is
often not implemented and you don't have
to implement it if you don't want to it
takes a string and then returns a monad
now the funny thing is that the default
implementation is that fail passes the
string to the function error and error
doesn't return a monad it actually just
ends your program right there with an
exception so yeah fail is used in order
to have some well error in your program
pop up for example let's say you do some
network code and some exception
shouldn't happen like a socket gets
closed prematurely for example then you
can just call fail for example and if
your monad can handle that if your Monat
can handle the error code and then
somehow encode it in its monad that's
great because then you get a monad but
if it doesn't implement the fail
function it will just pass it to error
and just end the program right there
okay so that's fail we've talked about
return and bind let's talk about the
last one I don't think this has a
special name at least I didn't find one
the greater greater so what is the
greater greater well I will call this
the anonymous bind or the unbind I don't
know well let's look at its
implementation its default
implementation of never changes it's the
following m2 n is nothing but binding m
to an anonymous function where we drop
the the name for this argument so we
just ignore the value that we get and
just continue with whatever we wanted to
do the important thing is that let's say
a fault he stayed happened
M then this faulty state is propagated
through right so then we don't even go
into this anonymous function but if
everything was alright we just ignore
its value so we can see here that if
something went wrong right so nothing
just means something went wrong now if
something went wrong right at the
beginning we don't return just one we
return nothing but if we have something
like this where like the second case
where we have a just one so something
went right and then adjust to then we
return just two and of course if we have
just one and we want to return nothing
we've returned on nothing so what is
this used for why do we need it well an
act with the anonymous bind to some
expression is the same as doing it in
the du notation with just no regard for
the value we get back where do we need
this well for example put string Ln and
put char in all of those output
functions that's a typical use case
where we don't care what the return is
but we still want to do some error
checking right because this put string
Ln could fail and in this case we should
propagate this error through and this
function does just that but it's just
not as messy with putting a name on
every value that we get because for
example in the case of put string Ln we
have an io of the empty tuple and we
already know what that value is it will
be the empty tuple so that's a case
where we just want to ignore that value
and that's how we do it with this
operator and now you don't have to
define this operator again it's just
automatically defined with the default
implementation that we have right here
okay so now we are almost done let's
talk about the monad loss because there
are some laws that if you have a monad
your monad should abide by these laws so
let's look at this the left identity is
the following returning a and then a
bind to K should be the
as ka because return a should return
just that the a right it should return a
monad with the encapsulated value a and
throw that to the function K so that
would be the same as just throwing that
value to the function K right it should
be the same okay so let's look at the
right identity where we have some monad
which we bind to return well of course
we get the value from that M and then we
put it into return which in that case
should just return the monad the
important thing is that this has to be
the same M right internal States
shouldn't change this identity should be
kept alive so and lastly monads should
be associative or the bind operator
should be associative it shouldn't
matter whether you first find m to some
function k and then bind that to H or if
you do it the other way around where you
say well M is bound to this function
that takes this argument then you know
does the
there's does the actual binding operator
and this just shouldn't matter the the
way of of doing this evaluation should
be irrelevant okay
Ask follow-up questions or revisit key timestamps.
The video explains the concept of monads in Haskell, focusing on their theoretical aspects and practical applications. It highlights that understanding the 'bind' operator (>>=) is key to understanding monads. The video illustrates this with examples using the 'Maybe' type, showing how 'bind' handles both 'Just' and 'Nothing' values, and how it can be used to propagate errors. It also introduces other monadic functions like 'return' and the less commonly used 'fail' and '>>'. The 'do' notation is presented as a more readable alternative to chaining bind operations. Finally, the video touches upon the monadic laws (left identity, right identity, and associativity) that any monad should satisfy.
Videos recently processed by our community