Scala中类型投影的正确限制是什么?

时间:2017-06-14 13:21:46

标签: scala type-projection

我在Scala中对类型投影的限制有困难。假设我有以下代码:

sealed trait Color
case object Red extends Color
case object Green extends Color
case object Blue extends Color

trait Item {
  type Colors <: Color
}

object RedGreenItem extends Item {
  type Colors = Red.type with Green.type
}

object Test {
  def foo[I <: Item, C >: I#Colors <: Color](item : I, color : C) : Unit = {
    // ...
  }

  def main(args : Array[String]) : Unit = {
    foo(RedGreenItem, Blue) // <- why does it compile?
  }
}

我真正希望实现的是能够传递给foo作为第二个参数,只传递Item#Colors中定义的颜色(因此限制C >: I#Colors)。 但事实证明,我也可以传递所有其他颜色(在这种情况下Blue)。

如果我像这样编写foo方法,我可以看到问题的根源:

def foo[I <: Item, C >: I#Colors <: Color : TypeTag](item : I, color : C) : Unit = {
  // ...
  println(s"type = ${typeOf[C]}")
}

// type = Color

因此,不是Blue.type,而是将类型推断为Color,因此任何颜色都可以。 如果我删除限制>: I#Colors,则正确确定类型:

def foo[I <: Item, C <: Color : TypeTag](item : I, color : C) : Unit = {
  // ...
  println(s"type = ${typeOf[C]}")
}

// type = Blue.type

所以,我的问题是:如何实现我的目标,让颜色的限制具有允许的类型而不是编译?非常感谢,对不起也许是一个愚蠢的问题。

2 个答案:

答案 0 :(得分:3)

您的类型Colors基本上是说您希望您的颜色同时为Red Green。但您要表达的是,它必须是Red Green

在Dotty,Scala的未来版本中,您可以根据需要相对直接地表达:

sealed trait Color
sealed trait Red extends Color
sealed trait Green extends Color
sealed trait Blue extends Color
case object Red extends Red
case object Green extends Green
case object Blue extends Blue

trait Item {
  type Colors <: Color
}

object RedGreenItem extends Item {
  type Colors = Red | Green
}

def foo[I <: Item](item: I)(color: item.Colors): Unit = ()

额外的密封特性是为了规避可能仍然被解除的其他限制。但这有效:

scala> foo(RedGreenItem)(Red) 
scala> foo(RedGreenItem)(Green) 
scala> foo(RedGreenItem)(Blue) 
-- [E007] Type Mismatch Error: <console>:13:18 ---------------------------------
13 |foo(RedGreenItem)(Blue)
   |                  ^^^^
   |                  found:    Blue.type
   |                  required: RedGreenItem.Colors
   |     

在当前的Scala中,我认为在一般情况下,对这种类型成员进行建模的最接近的事情是使用无形HList

import shapeless._, ops.hlist._

trait Item {
  type Colors <: HList
}

object RedGreenItem extends Item {
  type Colors = Red.type :: Green.type :: HNil
}

def foo[I <: Item, C <: Color](item: I, color: C)(implicit contains: Selector[item.Colors, C]): Unit = ()

再次按预期工作:

scala> foo(RedGreenItem, Red)

scala> foo(RedGreenItem, Green)

scala> foo(RedGreenItem, Blue)
<console>:27: error: Implicit not found: shapeless.Ops.Selector[RedGreenItem.Colors, Blue.type]. You requested an element of type Blue.type, but there is none in the HList RedGreenItem.Colors.
       foo(RedGreenItem, Blue)
          ^

还有其他方法可以表达这一点。例如,使用类型类而不是可覆盖的类型成员。我不建议使用HLists,但如果您想与类型成员一起使用,那就是如何做到这一点。

答案 1 :(得分:3)

对于像这样的东西拉动无形听起来像是一个巨大的矫枉过正。 这样的事可能吗?

trait Item {
  trait Acceptable[C <: Color]
}

object RedGreenItem extends Item {
  implicit object R extends Acceptable[Red.type]
  implicit object G extends Acceptable[Green.type]
}

def foo[I <: Item, C <: Color : I#Acceptable](i: I, c: C) = println(c)

foo(RedGreenItem, Red) 
// Red

foo(RedGreenItem, Blue)
error: could not find implicit value for evidence parameter of type RedGreenItem.Acceptable[Blue.type]