I'm having trouble deciphering the following function. Particularly, the flatMap line - where are the 'aa' and 'bb; variables coming from, and how is this executed? Maybe if someone could explain in words what is happening with this code it would be helpful. I'm just really struggling how to read this syntax.
def map2[EE >: E, B, C](b: Either[EE, B])(f: (A, B) => C): Either[EE, C] =
this flatMap(aa => b map (bb => f(aa, bb)))
Here is the flatMap function:
def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] = this match {
case Right(r) => f(r)
case Left(e) => Left(e)
}
and map:
def map[B](f: A => B): Either[E, B] = this match {
case Right(r) => Right(f(r))
case Left(e) => Left(e)
}
Looks like all these functions are defined for Either class. Either can be in two 'states' - Right - correct value and Left which interpreted ass error.
Lets go from the map definition. We have Either parametrized with type A. map accepts function converting type A to B. So map function checks if value is Right then it applies given function f to Right value, wraps it with Right and return. If error then just return error.
Right[String, Int](4).map(_*3) // gives Right(12)
Left[String, Int]("error").map(_*3) // gives Left("error")
flatMap is similar to map, but accepts function converting type A to Either of right type B, so unlike map we don't need to wrap right value
Right[String, Int](5).flatMap(item => Right(item*3)) //gives Right(15)
Left[String, Int]("error").flatMap(item => Right(item*3)) //gives Left("error")
As defined, map2 takes instance of Either b and function, converting pair of values of types A and B to some third value C, and return the result wrapped with Either. Let's take closer look at how it is implemented.
this.flatMap(aa => b map (bb => f(aa, bb)))
lets rewrite it with definition of flatMap:
this.match {
case Right(r) => b.map (bb => f(r, bb))
case Left(e) => Left(e)
}
And apply map:
this.match {
case Right(r) => {
b match {
case Right(br) => Right(f(r, br))
case Left(be) => Left(be)
}
}
case Left(e) => Left(e)
}
So there are three paths:
if right a and right b then apply function(a,b)
if right a and error b then error(b)
if error a then error a
Examples:
Right(7).map2(Right(4)){case (a,b) => a*b} // gives Right(28)
Right(7).map2(Left[String, Int]("failed")){case (a,b) => a*b} //gives Left("failed")
Left[String, Int]("error").map2(Left[String, Int]("failed")){case (a,b) => a*b} //gives Left("error")
Edit: Inner function explanation
You have to pass to a flatMap function with type:
A => Either[EE, C]
It means "take an argument of type A and convert it to result of type Either[EE,C]"
so you could define:
def map2[EE >: E, B, C](b: Either[EE, B])(f: (A, B) => C): Either[EE, C] = {
def func(aa:A):Either[EE, C] = {
b map (bb => f(aa, bb))
}
this flatMap(func)
}