是否可以在类型级别计算LUB?

时间:2018-03-07 20:01:27

标签: scala types

我需要获得作为参数给出的两种类型的最小上限。这样的事情:

class Holder[T] {
  type Shrink[S] = ???
}

其中???生成一些类型表达式,为TS类型生成最小上限。看似LUB_Magic[T,S]的东西。

我尝试过很多东西,但无法找到任何表达方式。

例如,如果预先定义类型AB

final case class Solve() {
  val query = (x : Any) => x match {
    case a : A => a
    case b : B => b
  }
  type Q = query.type
}

将根据最小上限生成类型。

但是如果我尝试像class Solve[A,B]()那样对它进行参数化,那么类型将是Any而不是最小上限。 gist solution

也是如此

是否有可能在类型级别获得最小上限?

1 个答案:

答案 0 :(得分:1)

如果您的主要目标是在query中使用正确的结果类型,那么您只需将A <:< CB <:< C的隐式证据添加到query的调用中即可。我已将这两种类型的证据分组到一个名为Lub的类中:

class Lub[A, B, C](val ev1: A <:< C, val ev2: B <:< C)

object Lub {
  implicit def lub[C, A <: C, B <: C]: Lub[A, B, C] = 
    new Lub[A, B, C](implicitly, implicitly)
}

现在您可以使用它来确保query返回正确的函数类型:

import scala.reflect.ClassTag

case class Solve[A: ClassTag, B: ClassTag]() {
  def query[C](implicit lub: Lub[A, B, C]): Any => C = {
    (x: Any) => x match {
      case a : A => lub.ev1(a)
      case b : B => lub.ev2(b)
    }
  }
}

val s1 = Solve[Int, Float]
val q1: Any => AnyVal = s1.query // typechecks

如果您坚持qval且最低上限为Solve的类型成员而不是在呼叫网站上推断它,您可以执行以下操作:

abstract class Solve2[A: ClassTag, B: ClassTag] {
  type C
  def query: Any => C
}

object Solve2 {
  final class Solve2Builder[A: ClassTag, B: ClassTag] {
    def build[X](implicit lub: Lub[A, B, X])
    : Solve2[A, B] { type C = X } = new Solve2[A, B] {
      type C = X
      val query: Any => C = _ match {
        case a: A => lub.ev1(a)
        case b: B => lub.ev2(b)
      } 
    }
  }
  def apply[A: ClassTag, B: ClassTag]: Solve2Builder[A, B] = new Solve2Builder[A, B]
}

现在Solve2[A, B]{ type C = ... }是使用随播对象创建的构建器构建的,A <:< CB <:< C的隐式证据只能提供一次:

val s2 = Solve2[Int, Float].build
val q2: Any => AnyVal = s2.query

请注意,使用ClassTag的所有这些操作只会带您到目前为止:您将能够区分IntFloat,但您无法区分List[Int]List[Float]。在进一步调查之前,您可以仔细查看shapeless提供的内容。