of
method we've placed on each of our types. Turns out, it is not there to avoid the new
keyword, but rather to place values in what's called a default minimal context. Yes, of
does not actually take the place of a constructor - it is part of an important interface we call Pointed.A pointed functor is a functor with anof
method
IO
and Task
's constructors expect a function as their argument, but Maybe
and Either
do not. The motivation for this interface is a common, consistent way to place a value into our functor without the complexities and specific demands of constructors. The term "default minimal context" lacks precision, yet captures the idea well: we'd like to lift any value in our type and map
away per usual with the expected behaviour of whichever functor.Left.of
doesn't make any sense. Each functor must have one way to place a value inside it and with Either
, that's new Right(x)
. We define of
using Right
because if our type can map
, it should map
. Looking at the examples above, we should have an intuition about how of
will usually work and Left
breaks that mold.pure
, point
, unit
, and return
. These are various monikers for our of
method, international function of mystery. of
will become important when we start using monads because, as we will see, it's our responsibility to place values back into the type manually.new
keyword, there are several standard JavaScript tricks or libraries so let's use them and use of
like a responsible adult from here on out. I recommend using functor instances from folktale
, ramda
or fantasy-land
as they provide the correct of
method as well as nice constructors that don't rely on new
.IO
trapped inside another IO
because print
introduced a second IO
during our map
. To continue working with our string, we must map(map(f))
and to observe the effect, we must unsafePerformIO().unsafePerformIO()
.map
three times to get at the value - we'd only just met. This pattern will arise time and time again and it is the primary situation where we'll need to shine the mighty monad symbol into the night sky.map
to get at the inner value. We can dry our eyes, take a deep breath, and use a method called join
.join
. This ability to join together, this functor matrimony, is what makes a monad a monad. Let's inch toward the full definition with something a little more accurate:Monads are pointed functors that can flatten
join
method, has an of
method, and obeys a few laws is a monad. Defining join
is not too difficult so let's do so for Maybe
:Maybe(Maybe(x))
then .$value
will just remove the unnecessary extra layer and we can safely map
from there. Otherwise, we'll just have the one Maybe
as nothing would have been mapped in the first place.join
method, let's sprinkle some magic monad dust over the firstAddressStreet
example and see it in action:join
wherever we encountered the nested Maybe
's to keep them from getting out of hand. Let's do the same with IO
to give us a feel for that.getItem
returns an IO String
so we map
to parse it. Both log
and setStyle
return IO
's themselves so we must join
to keep our nesting under control.join
right after a map
. Let's abstract this into a function called chain
.chain
called >>=
(pronounced bind) or flatMap
which are all aliases for the same concept. I personally think flatMap
is the most accurate name, but we'll stick with chain
as it's the widely accepted name in JS. Let's refactor the two examples above with chain
:map/join
with our new chain
function to tidy things up a bit. Cleanliness is nice and all, but there's more to chain
than meets the eye - it's more of a tornado than a vacuum. Because chain
effortlessly nests effects, we can capture both sequence and variable assignment in a purely functional way.compose
, but we'd need a few helper functions and this style lends itself to explicit variable assignment via closure anyhow. Instead we're using the infix version of chain
which, incidentally, can be derived from map
and join
for any type automatically: t.prototype.chain = function(f) { return this.map(f).join(); }
. We can also define chain
manually if we'd like a false sense of performance, though we must take care to maintain the correct functionality - that is, it must equal map
followed by join
. An interesting fact is that we can derive map
for free if we've created chain
simply by bottling the value back up when we're finished with of
. With chain
, we can also define join
as chain(id)
. It may feel like playing Texas Hold em' with a rhinestone magician in that I'm just pulling things out of my behind, but, as with most mathematics, all of these principled constructs are interrelated. Lots of these derivations are mentioned in the fantasyland repo, which is the official specification for algebraic data types in JavaScript.Task
's chained in a sequence of asynchronous actions - first it retrieves the user
, then it finds the friends with that user's id. We use chain
to avoid a Task(Task([Friend]))
situation.querySelector
to find a few different inputs and create a welcoming message. Notice how we have access to both uname
and email
at the innermost function - this is functional variable assignment at its finest. Since IO
is graciously lending us its value, we are in charge of putting it back how we found it - we wouldn't want to break its trust (and our program). IO.of
is the perfect tool for the job and it's why Pointed is an important prerequisite to the Monad interface. However, we could choose to map
as that would also return the correct type:Maybe
. Since chain
is mapping under the hood, if any value is null
, we stop the computation dead in its tracks.map
when returning a "normal" value and chain
when we're returning another functor. In the next chapter, we'll approach Applicatives
and see nice tricks to make this kind of expressions nicer and highly readable.map
or chain
(soon we'll see more container methods). We can greatly improve debugging with tricks like implementing inspect
and we'll learn how to create a "stack" that can handle whatever effects we throw at it, but there are times when we question if it's worth the hassle.readFile
uses Either
to validate the input (perhaps ensuring the filename is present), readFile
may error when accessing the file as expressed in the first type parameter of Task
, and the upload may fail for whatever reason which is expressed by the Error
in httpPost
. We casually pull off two nested, sequential asynchronous actions with chain
.upload
function is written against generic interfaces and not specific one-off apis. It's one bloody line for goodness sake.join
the outer two M
's of M(M(M a))
first then cruise over to our desired M a
with another join
. Alternatively, we can pop the hood and flatten the inner two M
's with map(join)
. We end up with the same M a
regardless of if we join the inner or outer M
's first and that's what associativity is all about. It's worth noting that map(join) != join
. The intermediate steps can vary in value, but the end result of the last join
will be the same.M
, of
and join
amounts to id
. We can also map(of)
and attack it from the inside out. We call this "triangle identity" because it makes such a shape when visualized:of
does indeed drop our M a
in another M
container. Then if we move downward and join
it, we get the same as if we just called id
in the first place. Moving right to left, we see that if we sneak under the covers with map
and call of
of the plain a
, we'll still end up with M (M a)
and join
ing will bring us back to square one.of
, however, it must be the specific M.of
for whatever monad we're using.Left
entered the picture.safeProp
and map/join
or chain
to safely get the street name when given a usersplit
and last
to obtain the
basename from a filepath.