【问题标题】:In scala, how to make type class working for Aux pattern? - Part 2在 scala 中,如何使类型类适用于 Aux 模式? - 第2部分
【发布时间】:2021-01-22 23:07:57
【问题描述】:

这是一个后续问题:In scala, how to make type class working for Aux pattern?

考虑以下示例:

  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
  }

TypeClasses 的所有实现都包含其底层TypeClass 的隐式范围,其中T1ForH 的最简单和具体的定义,不幸的是它不起作用。 @Dan Simon 提出了一项改进(在 T2 中),它使用类型参数允许 spark 编译器发现 ForH &lt;:&lt; Base.Aux[HNil]

现在想象一下,我想扩展@Dan Simon 的解决方案,以便类型类适用于所有类,如ForH 用于不同类型的 HList(HNil 的超特征)。 2个自然扩展分别在T3T4

不幸的是,它们都不起作用。 T4 可以解释为ForH &lt;:&lt; Aux[HList] 无效,但T3 不能以此为借口。另外,也没有办法改进编译成功。

为什么类型类召唤算法失败了?应该怎么做才能使类型类模式真正起作用?

【问题讨论】:

  • 仅供参考,T3T4 在 Scala 3 (3.0.0-M3) 中似乎都可以正常工作。所以也许这真的只是 Scala 2 的一个错误/限制。
  • 哇...我认为 T3 不应该工作。那里发生了什么:-|
  • 如果H 没有用在隐式参数或隐式返回类型中,那么编译器就没有什么可以继续推断H了。
  • @Jasper-M(叹息)你可能是对的,我浏览了整个shapeless的源代码,到目前为止只在隐式参数中找到了Aux
  • @DanSimon,我会尝试详细说明:ForH <: aux dotty>

标签: scala typeclass abstract-data-type type-alias scala-2.13


【解决方案1】:

同样,T1.summon(v) 无法编译,因为 T1.t1 不是候选者,手动解析的 T1.summon(v)(T1.t1) 无法编译。

对于T3T4 T3.t1[HNil, Base.ForH]T4.t1[HNil, Base.ForH] 将是候选对象

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

但问题是 H 被首先推断出来,它被推断为Nothingt1[Nothing, Base.ForH] 不满足类型界限。

所以问题不在于隐式解析算法,没关系,问题在于类型推断(我们都知道它在 Scala 中非常弱)。

如果修改T3.t1T4.t1,可以防止H被推断为Nothing太快

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-20
    • 1970-01-01
    • 2021-02-07
    • 2016-11-27
    相关资源
    最近更新 更多