Search code examples
scalafunctional-programmingaux-pattern

Aux pattern not working for nested implicits


I have the following Types

sealed trait Types

object Types {
  case class A(value: Int) extends Types

  case class B(value: String, flag: Boolean) extends Types

  case class L(value: Types, n: Int) extends Types
}

I want to have a generate method that will generate an output based on the input type, and the output might have different types. I tried to use the Aux pattern and for type A and type B the values are generated.

trait Generate[In] {
  type Out

  def generate(in: In): Out
}

object Generate {

  type Aux[I, O] = Generate[I] {type Out = O}

  implicit object AGenerate extends Generate[Types.A] {
    override type Out = Int

    override def generate(a: Types.A): Out = a.value
  }

  implicit object BGenerate extends Generate[Types.B] {
    override type Out = String

    override def generate(b: Types.B): Out = if (b.flag) b.value else "nope"
  }

//  implicit def lGenerate[T, O](implicit aux: Aux[T, O]): Generate[Types.L] = new Generate[Types.L] {
//    override type Out = List[O]
//
//    override def generate(in: Types.L): Out = List.fill(in.n)(aux.generate(in.value))
//  }

  implicit class RichGenerator[I](in: I) {
    def generate[O](implicit aux: Aux[I, O]): O = aux.generate(in)
  }
}

object Test extends App {
  import Types._
  import Generate._

  val a = A(1)
  val b = B("yep", flag = true)

  println(a.generate) // prints 1: Int
  println(b.generate) // prints "yep": String
}

However, when I try to generate a value for type L, which depends on Types, then the implicit resolution fails.

My goal is to obtain the following outputs:

println(L(a, 3).generate) \\ prints List(1,1,1)
println(L(b, 2).generate) \\ prints List("yep", "yep")
println(L(L(a,3), 2).generate) \\ prints List(List(1,1,1), List(1,1,1)) 

Solution

  • You probably want to properly link the Aux types together.

    First, let's make L generic:

    final case class L[T <: Types](value: T, n: Int) extends Types
    

    Then, let's properly define its Generator instance:

    implicit def lGenerate[T <: Types, O](implicit aux: Aux[T, O]): Aux[Types.L[T], List[O]] =
      new Generate[Types.L[T]] {
        override type Out = List[O]
      
        override def generate(in: Types.L[T]): Out =
          List.fill(in.n)(aux.generate(in.value))
      }
    

    You can see the code running here.