I work at a scala shop. Probably the typical scala shop --most people aren't that familiar with scala, certainly not FP, and tend to have Java backgrounds or are on some other team (I'm looking at you go) and have somehow inherited a Scala service. Needless to say I spend a lot of time on education.
Recently I was hit with a whack of questions around dealing with nested types --we can say nested effects here. Nothing crazy but it was tripping up people new to the language and programming with effects like
List[Future[Either[Error, List[Either[Serror, ParsedResponse]]]]]
In this case it was taking some sort of list of requests, chunking them into batches, making some remote call per batch to some external API that supported batched requests. An entire batch may fail or a batch may succeed but individual elements of the returned json array may have failed for one reason or another. People were contorting themselves into knots over this doing what everyone does when they are new (myself included the first time I hit something like this in a job interview):
- trying to make intermediate variables but from the outside in
- resulting in many nested
map(_.map(_.map(_.map...)))
kind of calls
- resulting in many nested
- trying to pattern match their way out of it
The real trick is knowing about Travsere and/or sequence. I won't get into the details (but I do give lunchtime workshops on this at work!) other than it's a convienent way to swap two contexts:
// have: List[Option[Int]] val xs = List(1.some, 2.some, 3.some, 4.some) // want: Option[List[Int]] xs.sequence // res0: Option[List[Int]]
This works for most containers you would want it to. In the standard library it's really for Futures + Option or List. Cats defines it much more generally.
Sequence is simply travserse(identity)
. If you find yourself mapping a
function and then using sequence you can replace that with a single call to
traverse.
Beyond that, working knowledge of
monoids is handy
especially for how folding a monoid works (.combineAll
is so handy).
As a result of all these questions I built out a workshop that I ask people to do. There are five questions around dealing with variations of the above nested type and getting it to some other type with some design decisions along the way:
- if any future fails or something fails to parse the entire thing
- if any future fails fail the entire thing but parsing errors are ok
- as above, but collect all the parsing errors etc.
Give it a whirl --try to make all the tests go green.
The workshop can be found here and the solutions can be found here