Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
477 views
in Technique[技术] by (71.8m points)

typeclass - In scala, how to make type class working for Aux pattern? - Part 2

This is a follow up question of: In scala, how to make type class working for Aux pattern?

Considering the following example:

  trait Base {

    type Out
    def v: Out
  }

  object Base {

    type Aux[T] = Base { type Out = T }
    type Lt[T] = Base { type Out <: T }

    class ForH() extends Base {

      final type Out = HNil

      override def v: Out = HNil
    }

    object ForH extends ForH
  }

  trait TypeClasses {

    class TypeClass[B]

    def summon[B](b: B)(implicit ev: TypeClass[B]): TypeClass[B] = ev
  }

  object T1 extends TypeClasses {

    implicit def t1: TypeClass[Base.Aux[HNil]] = new TypeClass[Base.Aux[HNil]]

    implicit def t2: TypeClass[Int] = new TypeClass[Int]
  }

  object T2 extends TypeClasses {

    implicit def t1[T <: Base.Aux[HNil]]: TypeClass[T] = new TypeClass[T]
  }

  object T3 extends TypeClasses {

    implicit def t1[
        H <: HList,
        T <: Base.Lt[H]
    ]: TypeClass[T] = new TypeClass[T] {

      type HH = H
    }
  }

  object T4 extends TypeClasses {

    implicit def t1[
        H <: HList,
        T <: Base.Aux[H]
    ]: TypeClass[T] = new TypeClass[T] {

      type HH = H
    }
  }

  it("No Aux") {

    val v = 2

    T1.summon(v) // works
  }

  it("Aux1") {

    val v = new Base.ForH()

    T1.summon(v) // oops
    T1.summon(Base.ForH) // oops

    val v2 = new Base.ForH(): Base.Aux[HNil]
    T1.summon(v2) // works!
  }

  it("Aux2") {

    val v = new Base.ForH()

    T2.summon(v) // works
    T2.summon(Base.ForH) // works

    val v2 = new Base.ForH(): Base.Aux[HNil]
    T2.summon(v2) // works
  }

  it("Aux3") {

    val v = new Base.ForH()

    T3.summon(v) // oops
    T3.summon(Base.ForH) // oops

    val v2 = new Base.ForH(): Base.Aux[HNil]
    T3.summon(v2) // oops
  }

  it("Aux4") {

    val v = new Base.ForH()

    T4.summon(v) // oops
    T4.summon(Base.ForH) // oops

    val v2 = new Base.ForH(): Base.Aux[HNil]
    T4.summon(v2) // oops
  }

all implementations of TypeClasses contains an implicit scope of their underlying TypeClass, among them, the T1 is the most simple and specific definition for ForH, unfortunately it doesn't work. An improvement was proposed by @Dan Simon (in T2), it uses a type parameter to allow the spark compiler to discover ForH <:< Base.Aux[HNil]

Now imagine that I'd like to extend @Dan Simon's solution, such that the type class applies to all classes like ForH for different kinds of HList (a super trait of HNil). 2 natural extensions are in T3 & T4 respectively.

Unfortunately none of them works. The T4 can be explained by the fact that ForH <:< Aux[HList] is invalid, but T3 can't use this excuse. In addition, there is no way to improve it to compile successfully.

Why the type class summoning algorithm failed this case? And what should be done to make the type class pattern actually works?

question from:https://stackoverflow.com/questions/65853961/in-scala-how-to-make-type-class-working-for-aux-pattern-part-2

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Again, T1.summon(v) doesn't compile because T1.t1 is not a candidate, manually resolved T1.summon(v)(T1.t1) doesn't compile.

For T3 and T4 T3.t1[HNil, Base.ForH], T4.t1[HNil, Base.ForH] would be a candidate

T3.summon(v)(T3.t1[HNil, Base.ForH]) // compiles
T4.summon(v)(T4.t1[HNil, Base.ForH]) // compiles

but the trouble is that H is inferred first and it's inferred to be Nothing but t1[Nothing, Base.ForH] doesn't satisfy type bounds.

So the trouble is not with implicit resolution algorithm, it's ok, the trouble is with type inference (and all we know that it's pretty weak in Scala).

You can prevent H to be inferred too fast as Nothing if you modify T3.t1, T4.t1

object T3 extends TypeClasses {    
  implicit def t1[
    H <: HList,
    T <: Base/*.Lt[H]*/
  ](implicit ev: T <:< Base.Lt[H]): TypeClass[T] = new TypeClass[T] {
    type HH = H
  }
}

object T4 extends TypeClasses { 
  implicit def t1[
    H <: HList,
    T <: Base/*.Aux[H]*/
  ](implicit ev: T <:< Base.Aux[H]): TypeClass[T] = new TypeClass[T] {
    type HH = H
  }
}

T3.summon(v) // compiles
T4.summon(v) // compiles

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...