Connecting...

Pexels Photo 691710

Scala, the Aux pattern & path dependent types by Basement Crowd

Pexels Photo 691710

Delve into the AUX pattern with Basement Crowd.

They take a look at a problem and how exactly AUX pattern can rescue it. If you're familiar with path dependent types and abstract member types then this blog is for you!

 

'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.

 

The problem

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:
1 trait Generic[T] extends Serializable {
2  /** The generic representation type for {T}, which will be composed of {Coproduct} and {HList} types  */
3  type Repr
4
5  /** Convert an instance of the concrete type to the generic value representation */
6  def to(t : T) : Repr
7
8  /** Convert an instance of the generic representation to an instance of the concrete type */
9  def from(r : Repr) : T
10 }

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:

1 import shapeless._
2
3 case class Capybara(name: String, age: Int, awesome: Boolean)
4 val genericCapy = Generic[Capybara]
5
6 val hlistCapy = genericCapybara.to(Generator.generate[Capybara])

But if we take a closer look at the Generic implementation we see an abstract type member:

1 type Repr

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:

  1. 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)
  2. 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:

1 implicit def caseClassGenerator[T, L <: HList](implicit generic: Generic[T], lGen: Generator[generic.Repr]):Generator[T] = 
2   new Generator[T] {
3    override def generate = generic.from(lGen.generate)
4  }

Uh oh, that doesn’t compile

1 Error:(29, 1 40) illegal dependent method type: parameter may only be referenced in a subsequent parameter section

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:

1 type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 }

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:

1 implicit def caseClassGenerator[T, L <: HList](implicit generic: Generic.Aux[T, L], lGen: Generator[L]): Generator[T] = 
2   new Generator[T] {
3     override def generate = generic.from(lGen.generate)
4  }

 

This article was written by and posted originally on Basement Crowd Blog.