left
and right
are the important ones. Either
is useful without projections (mostly you do pattern matching), but projections are quite worthy of attention, as they give a much richer API. You will use joins much less.
Either
is often used to mean "a proper value or an error". In this respect, it is like an extended Option
. When there is no data, instead of None
, you have an error.
Option
has a rich API. The same can be made available on Either
, provided we know, in Either, which one is the result and which one is the error.
left
and right
projection says just that. It is the Either
, plus the added knowledge that the value is respectively at left or at right, and the other one is the error.
For instance, in Option
, you can map, so opt.map(f)
returns an Option
with f
applied to the value of opt
if it has a one, and still None
if opt
was None
. On a left projection, it will apply f
on the value at left if it is a Left
, and leave it unchanged if it is a Right
. Observe the signatures:
- In
LeftProjection[A,B]
, map[C](f: A => C): Either[C,B]
- In
RightProjection[A,B]
, map[C](f: B => C): Either[A,C]
.
left
and right
are simply the way to say which side is considered the value when you want to use one of the usual API routines.
Alternatives could have been:
- set a convention, as in Haskell, where there were strong syntactical reasons to put the value at right. When you want to apply a method on the other side (you may well want to change the error with a
map
for instance), do a swap
before and after.
- postfix method names with Left or Right (maybe just L and R). That would prevent using for comprehension. With
for
comprehensions (flatMap
in fact, but the for notation is quite convenient) Either
is an alternative to (checked) exceptions.
Now the joins. Left and Right means the same thing as for the projections, and they are closely related to flatMap
. Consider joinLeft
. The signature may be puzzling:
joinLeft [A1 >: A, B1 >: B, C] (implicit ev: <:<[A1, Either[C, B1]]):
Either[C, B1]
A1
and B1
are technically necessary, but not critical to the understanding, let's simplify
joinLeft[C](implicit ev: <:<[A, Either[C, B])
What the implicit means is that the method can only be called if A
is an Either[C,B]
. The method is not available on an Either[A,B]
in general, but only on an Either[Either[C,B], B]
. As with left projection, we consider that the value is at left (that would be right for joinRight
). What the join does is flatten this (think flatMap
). When one join, one does not care whether the error (B) is inside or outside, we just want Either[C,B]. So Left(Left(c)) yields Left(c), both Left(Right(b)) and Right(b) yield Right(b). The relation with flatMap is as follows:
joinLeft(e) = e.left.flatMap(identity)
e.left.flatMap(f) = e.left.map(f).joinLeft
The Option
equivalent would work on an Option[Option[A]]
, Some(Some(x))
would yield Some(x)
both Some(None)
and None
would yield None
. It can be written o.flatMap(identity). Note that Option[A]
is isomorphic to Either[A,Unit]
(if you use left projections and joins) and also to Either[Unit, A]
(using right projections).