18

This question may be asked and answered before, but I would like to understand this with an example and I could not reason out where the Aux pattern might be helpful! So here is the trait:

trait Foo[A] {
  type B
  def value: B
}

Why do I have a type that is kind of bound to the return type of the value function? What do I achieve doing this? In particular, where would I use such patterns?

joesan
  • 13,963
  • 27
  • 95
  • 232

2 Answers2

25

Imagine a typeclass for getting the last element of any tuple.

trait Last[A] {
  type B
  def last(a: A): B
}

object Last {
  type Aux[A,B0] = Last[A] { type B = B0 }

  implicit def tuple1Last[A]: Aux[Tuple1[A],A] = new Last[Tuple1[A]] {
    type B = A
    def last(a: Tuple1[A]) = a._1
  }

  implicit def tuple2Last[A,C]: Aux[(A,C),C] = new Last[(A,C)] {
    type B = C
    def last(a: (A,C)) = a._2
  }

  ...
}

The type B always depends on the type A, that's why A is an input type of the typeclass and B is an output type.

Now if you want a function that can sort any list of tuples based on the last element you need access to the B type in the same argument list. That's the main reason, in the current state of Scala, why you need the Aux pattern: currently it's not possible to refer to the last.B type in the same parameter list as where last is defined, nor is it possible to have multiple implicit parameter lists.

def sort[A,B](as: List[A])(implicit last: Last.Aux[A,B], ord: Ordering[B]) = as.sortBy(last.last)

Of course you can always write Last[A] { type B = B0 } out in full, but obviously that becomes very impractical very quickly (imagine adding a couple more implicit parameters with dependent types, something that's very common with Shapeless); that's where the Aux type alias comes in.

Jasper-M
  • 14,966
  • 2
  • 26
  • 37
  • 2
    You don't technically _need_ the `Aux` pattern to refer to `last.B` in the same parameter list; it's just preferred because it's much less verbose than `implicit last: Last[A] { type B = B0 }, ord: Ordering[B0]`. – breadmenace May 10 '17 at 19:40
  • 1
    @breadmenace Well yes, technically you never need the Aux type alias. I assume 'Aux pattern' to refer to the broader pattern in which the type alias is needed for having an acceptable syntax. – Jasper-M May 10 '17 at 19:51
  • 2
    newb question - why isn't the return type of the implicit defs (the ones using Aux) inferred from the computed value? – mtk May 11 '17 at 16:34
  • 2
    @user2359636 I'm pretty sure they actually are inferred if you let them, but it's bad practice not to add type annotations to implicits. I think dotty has already outlawed non-annotated implicits. – Jasper-M May 11 '17 at 16:48
12

Starting Scala 3 dependent types are supported within the same parameter list, which seems to make Aux pattern unnecessary, for example, Jasper-M's snippet simplifies to

trait Last[A]:
  type B
  def last(a: A): B

given [A]: Last[Tuple1[A]] with
  type B = A
  def last(a: Tuple1[A]) = a._1

given [A, C]: Last[(A,C)] with
  type B = C
  def last(a: (A,C)) = a._2

def sort[A](as: List[A])(using last: Last[A], ord: Ordering[last.B]) = as.sortBy(last.last)

sort(List(("ffle",3), ("fu",2), ("ker",1)))
// List((ker,1), (fu,2), (ffle,3))

Note the usage of last.B where last is a value coming from the same parameter list in

def sort[A](as: List[A])(using last: Last[A], ord: Ordering[last.B])
Mario Galic
  • 47,285
  • 6
  • 56
  • 98