When I first saw Scala code like this ~12 years ago, my initial thought was, “Wow, maybe I need to think about a different career”:
def race[A, B](lh: IO[A], rh: IO[B])(implicit cs: ContextShift[IO]): IO[Either[A, B]]
But in the end, as Rocky once said, it ain’t so bad. :)
(Image courtesy of the movie, Rocky III.)
In the following sections I’ll show how I break method signatures like this down in my mind these days.
1) See the different groups
First, train yourself to see the different groups in the Scala method signature. Here I separate the groups slightly and color-code them:
(Maybe an IDE can do this for you? If not, it should!)
2) `def` means you’re about to see a method definition
Next, start looking at each group, one at a time. In the first group,
def says, “You’re about to see a method definition”:
3) And then the method name
race is the name of the method:
(Note that this is a method that “races” two calculations, and returns the winner of that race.)
4) Warning, generic types ahead
In the third group,
[A, B] is the Scala way to say, “In the rest of this type signature, you’re going to see two generic types. We use
B because the types may be different”:
In some cases the types that are supplied may be the same, but because they can be different, we use A and B to note that they can be different.
Also, if we had one type we’d write
[A], and if there are three types we’d write
[A, B, C], etc.
(When generic types cause my brain problems, I replace them with something simpler, like
5) The first of two parameter groups
Scala lets you have two parameter groups, and the green group shows two parameters named
If the name
IO[A] causes your brain pain, it can help to temporarily think of the types as being
Option[String], or maybe
List[String]; anything that’s easier to grok at the moment.
6) The second parameter group (and an implicit)
The second parameter group is shown in blue. This one says there is a parameter named
cs that has the type
ContextShift[IO]. The parameter is
implicit, meaning a called can provide it explicitly, or not:
In regard to these parameter groups, the
implicit in Group 2 says, “You can call
race in two different ways in your code”:
// pass the `cs` param explicitly val result = race(lh, rh)(cs) // `cs` must be in scope and is pulled in automatically val result = race(lh, rh)
7) Lastly, the method return type
race method returns an
This is just an
Either[A, B] that’s wrapped in an
IO for safe keeping.
Either is a construct that returns one of two possible types, so in your code you just need to unwrap the
IO to get the
Either contents. This is like getting a gift that’s in a box, and when you open the first box you find a second box inside, and then your gift is inside that second box.
Think of IO as being an effect or action
In this particular example, IO is a little outside the scope of this discussion, but if it helps, you can think of
IO as being the holder/result of an effect or action. When running effects, libraries like Cats use the term also use the term “unsafe.” Therefore, it may help to think of synonyms for
IO, like these:
Effect[Either[A, B]] EffectHolder[Either[A, B]] EffectResult[Either[A, B]] ActionResult[Either[A, B]] ResultOfAnAction[Either[A, B]] Unsafe[Either[A, B]]
For more on
Either, see my articles:
- What do effect and effectful mean in functional programming?
- A Scala Either, Left, and Right example
Summary: Seeing the entire “race” method signature
As I mentioned before, this is a method that “races” two effects, and returns the winner of that race. Hopefully now you can see that all of this is expressed in the method’s type signature. Everything the method needs goes into its two parameter groups, and its result is contained inside the
And finally, if method signatures like these have made you think about a career change, I hope this helps. :)