2

一个简单的价值层次

想象一下这个简单的特征Value,其中每个实现类都有value某种类型T

trait Value {
  type T
  def value: T
}

我们有两个不同的实现类分别表示IntString值。

case class IntValue(override val value: Int) extends Value {
  override type T = Int
}

case class StringValue(override val value: String) extends Value {
  override type T = String
}

键入安全的值选择

如果我们有一个List值,我们希望有一种类型安全的方式来选择特定类型的所有值。类Values及其伴随对象帮助我们做到这一点:

object Values {
  private type GroupedValues = Map[ClassTag[_ <: Value], List[Value]]

  def apply(values: List[Value]): Values = {
    val groupedValues: GroupedValues = values.groupBy(value => ClassTag(value.getClass))
    new Values(groupedValues)
  }
}

class Values private (groupedValues: Values.GroupedValues) {
  // Get a List of all values of type V.
  def getValues[V <: Value : ClassTag] = {
    val classTag = implicitly[ClassTag[V]]
    groupedValues.get(classTag).map(_.asInstanceOf[List[V]]).getOrElse(Nil)
  }

  def getValue[V <: Value : ClassTag] = {
    getValues.head
  }

  def getValueOption[V <: Value : ClassTag] = {
    getValues.headOption
  }

  def getValueInner[V <: Value : ClassTag] = {
    getValues.head.value
  }
}

所有这些在 Scala 2.13 和 Dotty 0.20.0-RC1 中都可以正常工作,因此有一个混合值列表……</p>

val valueList = List(IntValue(1), StringValue("hello"))
val values = Values(valueList)

…我们可以选择元素并将它们作为正确的类型返回——所有这些都在编译时检查:

val ints: List[IntValue] = values.getValues[IntValue]
val strings: List[StringValue] = values.getValues[StringValue]

val int: IntValue = values.getValue[IntValue]
val string: StringValue = values.getValue[StringValue]

val intOption: Option[IntValue] = values.getValueOption[IntValue]
val stringOption: Option[StringValue] = values.getValueOption[StringValue]

val i: Int = values.getValueInner[IntValue]
val s: String = values.getValueInner[StringValue]

在 Dotty 中选择一个值Option[T]失败

但是,如果我们添加这个函数来选择值作为它们的T类型(即IntString)并将其作为Option…</p>

class Values ... {
  ...
  def getValueInnerOption[V <: Value : ClassTag] = {
    getValues.headOption.map(_.value)
  }
}

…然后在 Scala 2.13 中一切正常:

val iOption: Option[Int] = values.getValueInnerOption[IntValue]
val sOption: Option[String] = values.getValueInnerOption[StringValue]

但在 Dotty 0.20.0-RC1 中,这不能编译:

-- [E007] Type Mismatch Error: getValue.scala:74:29 
74 |  val iOption: Option[Int] = values.getValueInnerOption[IntValue]
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |                             Found:    Option[Any]
   |                             Required: Option[Int]
-- [E007] Type Mismatch Error: getValue.scala:75:32 
75 |  val sOption: Option[String] = values.getValueInnerOption[StringValue]
   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |                                Found:    Option[Any]
   |                                Required: Option[String]

我们可以通过添加一个类型参数来解决这个问题,getValueInnerOption它将返回类型和抽象类型T联系在一起,并允许我们指定返回类型。

def getValueInnerOption[V <: Value {type T = U} : ClassTag, U]: Option[U] = {
  getValues.headOption.map(_.value)
}

不幸的是,这意味着我们将不得不在调用站点添加T(ie Intor String) 的实际类型,这很遗憾,因为它只是样板文件。

val iOption: Option[Int] = values.getValueInnerOption[IntValue, Int]
val sOption: Option[String] = values.getValueInnerOption[StringValue, String]

Dotty 中的错误或该怎么办?

似乎 Dotty 已经知道上限T是什么,但无法将该知识传播到函数的结果类型。String如果尝试从 an请求 a 可以看出这一点IntValue

-- [E057] Type Mismatch Error: getValue.scala:75:39 
75 |  val wtf = values.getValueInnerOption[IntValue, String]
   |                                       ^
   |Type argument IntValue does not conform to upper bound Value{T = String} 

那么原始代码(没有类型参数U)是否可以在最终的 Scala 3.0 中工作,还是需要以不同的方式编写?

4

2 回答 2

1

在 Dotty 中尝试匹配类型作为类型投影的替代品

type InnerType[V <: Value] = V match {
  case IntValue    => Int
  case StringValue => String
}

trait Value {
  type This >: this.type <: Value
  type T = InnerType[This]
  def value: T
}

case class IntValue(override val value: Int) extends Value {
  override type This = IntValue
}

case class StringValue(override val value: String) extends Value {
  override type This = StringValue
}

def getValueInner[V <: Value { type This = V } : ClassTag]: InnerType[V] = {
  getValues.head.value
}

def getValueInnerOption[V <: Value { type This = V } : ClassTag]: Option[InnerType[V]] = {
  getValues.headOption.map(_.value)
}
于 2019-11-29T13:27:29.387 回答
1

_.value具有默认情况下不推断的依赖函数类型,但您可以指定它:

def getValueInnerOption[V <: Value : ClassTag] = {
  getValues.headOption.map((_.value): (v: V) => v.T)
}

接着

val iOption: Option[Int] = values.getValueInnerOption[IntValue]
val sOption: Option[String] = values.getValueInnerOption[StringValue]

编译。

但问题是我不确定它(和getValueInner是否应该工作。因为它们的推断返回类型涉及V#T(如果您给出错误的返回类型,您可以在错误消息中看到它们),并且尝试显式指定它们给出

V 不是合法路径,因为它不是具体类型

(见http://dotty.epfl.ch/docs/reference/dropped-features/type-projection.html

于 2019-11-29T12:39:50.107 回答