Scala, the Aux pattern & path dependent types
In our last post about Shapeless, we casually mentioned the Aux pattern was being used:
L is a path dependent type on Generic[T], so we use the AUX pattern here so we can reference L
Rather than just leaving that out there, with no explanation, we wanted to delve into that pattern a little deeper.
In his blog series “A Neophyte’s guide to scala”, Daniel Westheide gives a good overview of path dependent types and abstract member types, so we won’t go through the details of explaining those concepts (read Daniel’s blog if you need more info) – from here on out we will assume you are familiar with those concepts.
Let’s take a look at that Generic[T] class that we saw in Shapeless – it’s a pretty simple class with a single type parameter:
For the Generic instance, type T refers to the case class that we want to convert to and from a HList, remembering our last example we have instances of Generic as follows:
But if we take a closer look at the Generic implementation we see an abstract type member:
This trait is mixing two similar approaches: type parameters and abstract type members (Repr is a path-dependent type). You might notice that Repr could also be encoded as a type parameter like Generic[T, Repr] instead of mixing these approaches, and the rest of the trait would be the same.
However there are a couple of good reasons for Repr being an abstract type member:
- Repr is dependent on T, and can be derived from T. That is, for any case class T, there is only ever one possible Repr (Repr is the HList representation of the class)
- Having Repr as a type parameter would have made all the client code a lot more ugly and verbose, as we could no longer just ask for a Generic[Capybara] but would also have to describe the complete HList as a type in that signature.
Now let’s think back to our Generator example from the Shapeless post – we wanted to create an instance of our Generator[T] type-class that could handle any case class, and to do that we had to ensure that there would be a Generic that could convert our T to a HList, and also that we had another Generator instance that could handle the resulting HList representation (our Repr for the Generic).
Let’s try and do that:
Uh oh, that doesn’t compile
As the compile error states, we can’t refer to dependent types from the same parameter section (if however, we were passing generic as a normal argument, then within the implicit parameters section, we could refer to that type).
The Aux pattern to the rescue
This pattern is a way to get around the limitation described above whilst doing type-level programming and using path-dependent types. It’s incredibly simple:
The above is the actual Shapeless implementation – as you can see, we just create a type alias that simply lifts Repr to a type parameter. Using this pattern, we have the best of both worlds: we can reference by either just the case class, or if we need a reference to the Repr parameter we can use the Aux pattern.
Finally, we can use this Aux type in our function, so we get a handle on L, and use it to get an implicit Generator for it:
Before joining, Rob spent 4 years working as a senior engineer at an investment management start-up in London, building a cutting edge trading platform and market place for money managers. Prior to that, Rob spent 6 years working as a technical consultant at Accenture, working on a range of technology-driven, client-facing projects based across Europe. Rob holds a BSc in Artificial Intelligence and Computer Science from the University of Birmingham.