Since we have covered Option previously, it’s time to show you other types just like Option
that are prevalent in Scala’s standard library and third party code. We will also see how these types are part of a larger set of objects and they have this name that starts with M that, unfortunately, shuts people off when they hear about it so I won’t type it here just yet.
Either[T]
one thing or the otherIf you look at Either
’s Scala Docs you will see it says it is a disjoint union. In math, a disjoint union is a collection of two sets that have no items in common, when we translate this to Scala, imagine that each set is a different type and the items are instances of these two types.
Saying we return an Either[String,Int]
means that we could return an Int
or a String
, they’re both different types and don’t share much. In fact, we usually use them for completely different things, so why would we ever care about using something like this?
One of the most common cases is because we use exceptions when we shouldn’t. If you wrote Objective-C code somewhere in this or in a past life, you have probably seen NSError objects lying around in your codebase. If you have never seen Objective-C, don’t despair, NSError
is an object that’s used throughout Objective-C APIs to give you better information about errors that have happened while you have tried to do something.
NSError
is not an exception object, you don’t throw, raise, catch or rescue NSError
objects (Objective-C has it’s own NSException class for that), they’re not meant for that. They’re meant to convey more information about why your code failed to do whatever you wanted it to do.
And why is it not an exception, you ask?
Because exceptions are for exceptional cases, for when something is really broken, NSError
is used when the other case is not an exceptional case but something that happens with some frequency and that you should handle it gracefully. The main goal here is to make it clear from the types you use that a method could have two different outcomes and they are common enough that you should care about them both and make sure your code is capable of handling them.
Let’s get to the code now:
The pattern is mostly what we have seen before, you have the base sealed trait
and then you have case classes we can use to pattern match on the values. We also have a fold
method that can be used to produce an output to the computation for both cases, mostly a shortcut to pattern matching.
Let’s look at an example of using it:
Here we have a simple ServiceClient
class that could be an HTTP client talking to some external service. This external service could respond with specific error codes depending on what the client is asking and how he is asking. These error codes don’t need to be exceptions, that’s just how HTTP works, we could have a success and receive the user data or get one of the non-success HTTP response codes with a message and that’s this message that makes either useful here.
If we were just looking at “user” and “no user” scenarios, we could easily model this interaction using Option
, but if we did that we would lose the ability to show the detailed message produced by the server
to the client of our app. He would just see that “there’s no user”, without knowing what is actually going on.
Let’s fold on the data now:
As you can see, folding on Either
is specially nice if you have to build some output message based on the objects being returned. This could be a command line app, a webapp or anywhere where you had to render some message in all cases.
So, whenever you have a method that could possibly return two different types, Either
will be there for you.
Try[T]
to compose on exceptionsPreviously, Either
was also used to handle the exception case, you call a method that could possibly raise an exception, you would use Either
, with Left
being the error and Right
the expected value. Since Scala 2.10, this usage was replaced by the use of Try
. A Try
wraps a computation that could either result in a value or in an exception being thrown. Think about it as a specialization for Either
, while you would use Either
whenever you have to return one thing or the other, with Try
it’s always one thing or an error.
Being specific like this, means that using Try
for the error case is much simpler than doing the same with Either
. Let’s look at how we could implement it:
And again we have that same pattern, one sealed trait and two case classes. Just like Option[T]
, the main advantage of using Try[T]
is being able to compose with the value without caring about what’s inside. It’s just like the Schrodinger’s cat story, you keep working with the box and delay opening it up until it’s completely necessary to know what’s happening in there.
And how do we do that? We map
and flatMap
with the computation. It doesn’t matter if it’s a Success
or a Failure
, when we use map
and flatMap
we compose on the value and wait for it to be something. Let’s look a bit at the usage of Try[T]
:
Using Try[T]
is a bit different from using Either
or Option
because you can just wrap a computation around it instead of creating a Success
or a Failure
manually. Given we know some computation can fail, we can just use the Try[T].apply
method at the companion object and it will automatically wrap the correct value or the error and return a Try[T]
for us.
While when we write Try( "abc".toInt )
it looks like we are running "abc".toInt
before sending it in as a parameter, in reality, the Scala compiler knows that we are expecting a => U
function and automatically wraps that code into a closure. This is called thunking or a lazy parameter, since it is not evaluated before being given to the method. In Scala lingo, you will also see people calling this a pass by name, since, instead of sending it the computed value, you are sending in a named function that will, in turn, produce the computed value.
The NonFatal
reference at the try/catch
block is to mean that we want to catch any exception that is actually recoverable. Some errors (like JVM errors) are not really safe to be caught so we just use this NonFatal
here at Try[T]
to make sure we don’t try to catch them as well and catch only exceptions that we can recover.
And while this is good and all, you can also manually build a try from Success
or Failure
as well if you would like to. If you are working with concurrent systems, where you’re handling Future[T]
and Promise[T]
you will most likely make heavy use of Try[T]
results for your computations.
Let’s do some composing:
And since we have map
and flatMap
implemented, we can both manually map the calls and also use for comprehensions (flatMap
) on our results.
And other than making it possible for you to wrap a computation that could possibly fail, the most important feature of using Try[T]
is that the exception information is stored to whoever needs to unwrap the value at the end of the chain. You don’t have to do the error prone catch(Exception e) throw new SomeOtherException(e)
all the way up to the code that actually needs the value (or the error), you just use a Try[T]
at all the intermediate steps of the computation and the top level code that will grab the value can look at the real error (and not some infinite chain of bogus exceptions) and behave accordingly.
M
word?Well, if you came this far, you have seen the pattern already. Let’s look at what it looks like:
And there it is. Monads are containers for values and the container must have a flatMap
(you can also find this being called bind
) and a unit(v)
operation.
The type declarations are enough for us to figure out what these two methods mean, flatMap
means transforming a value that is inside a monad into another value still inside the same type of monad. Why is it necessary to keep ir wrapped? Think about Schrodinger’s cat again, we want to delay the decision of knowing what’s inside the box as much as possible so that I can do something like:
The same would be true if we were dealing with Option[T]
. When composing many options, we might have a None
somewhere but we don’t want to care about that until it is necessary to look inside the box.
Now that we know that flatMap
means for a monad, what is this unit(v)
? Well, it’s what you use to wrap a value. It’s the Some(v)
, the Success(r)
and even the List(v)
(yes, list is a monad as well). In Scala, all monads will have flatMap
implemented directly, but each one will have it’s own version of unit(v)
. Usually, it’s going to be some constructor or apply
method at a companion object. Still, they will have have these two operations.
And what about map
? Aren’t we using map
everywhere?
Well, map
is just a special case for flatMap
, here’s how we could write map
for Option[T]
:
In our Monad[T]
implementation this would look like:
Exactly the same code. So we don’t actually need map
but having it defined as well is a nice shortcut for some common patterns as well.
To define something to be a monad, you use the 3 monad laws, the first is associativity:
Using Option[T]
, we could have this as:
So, it doesn’t matter if you do the flatMap
inside another flatMap
or if you do it at the result produced. It has to produce exactly the same values.
The second is the left unit law, here’s the definition:
Which would become the following for Option[T]
:
So, getting a monad and running flatMap
on it with some function is the same as calling the some function with the wrapped x
value.
And the third law is the right unit law, let’s look at the definition:
Which in turn would be defined as follows for Option[T]
:
And for this last one, we mean that having a monad, running flatMap
on unit, we should have the same as unit(x)
.
To validate these laws, you can’t just go through these unit tests, you would have to replace all function calls with the actual code (which we won’t do here) and check that the expressions generated are compatible (as you would do in a mathematical equation), but if you can guarantee that your container object respects these 3 laws then you have a monad in place.
As you can see, there isn’t anything specially complicated about monads, they’re just a container type that follows a collection of rules (which are very important, the right unit rule, for instance, allows us to use monads in for-comprehensions) and are used as container types.
As an aside, there is some debate as to if Try[U]
is a full monad or not. The problem is that if you think unit(x)
is Success(x)
, then exceptions would be raised when you try to execute the left unit law since flatMap
will correctly wrap an exception but the f(x)
might not be able to do it. Still, if you assume that the correct unit is Try.apply
then this would not be an issue. In any case, while Try
might not be a pure monad, it’s close enough so you can use it much the same way.
Didn’t see the other posts about this? Check the whole list:
For more about monads and Scala, check these ones: