>我有一个案例类定义如下
case class ChooseBoxData[T](index:T, text:String)
是否可以声明一个列表,以便列表只接受 ChooseBoxData[String] 和 ChooseBoxData[Int] 的类型?
我所期望的是这样的:
val specialList:List[some type declaration] = List(
ChooseBoxData[String]("some string","some string"),/* allow, because is ChooseBoxData[String]*/
ChooseBoxData[Int](12,"some string"), /* also allow, because is ChooseBoxData[Int]*/
ChooseBoxData[Boolean](true,"some string")/* not allow type other than ChooseBoxData[String] or ChooseBoxData[Int]*/
)
可能是这样的事情:
trait AllowableBoxData
object AllowableBoxData {
private of[T](cbd: ChooseBoxData[T]) = new ChooseBoxData(cbd.index, cbd.text)
with AllowableBoxData
implicit def ofInt(cbd: ChooseBoxData[Int]) = of(cbd)
implicit def ofString(cbd: ChooseBoxData[String]) = of(cbd)
}
现在你可以做这样的事情
val list: List[ChooseBoxData[_] with AllowableBoxData] = List(ChooseBoxData("foo", "bar"), ChooseBoxData(0, "baz")
但不是val list: List[AllowableBoxData] = List(ChooseBoxData(false, "baz"))
此外,如果您希望声明函数参数而不仅仅是变量,那么将有一个更优雅的解决方案:
trait CanUse[T]
implicit case object CanUseInt extends CanUse[Int]
implicit case object CanUseString extends CanUse[String]
def foo[T : CanUse](bar: List[ChooseBoxData[T]])
这是我想到的:
首先,我们创建以下代数数据类型 (ADT):
sealed trait StringInt
case class Stringy(s : String) extends StringInt
case class Inty(s : Int) extends StringInt
并按如下方式定义ChoooseBoxData
:
case class ChooseBoxData(index : StringInt, text : String)
然后,我们定义以下隐式,以将范围内的Int
和String
转换为定义的 ADT:
object CBImplicits {
implicit def conv(u : String) = Stringy(u)
implicit def conv2(u : Int) = Inty(u)
}
现在,我们可以强制执行问题中的要求。下面是一个示例:
import CBImplicits._
val list = List(ChooseBoxData("str", "text"),
ChooseBoxData(1, "text"),
ChooseBoxData(true, "text"))
尝试运行上述内容时,编译器将抱怨类型不匹配。但这将编译并运行:
List(
ChooseBoxData("str", "text"),
ChooseBoxData(1, "text"),
ChooseBoxData(12, "text2"))
这导致:
a: List[ChooseBoxData] =
List(ChooseBoxData(Stringy(str),text), ChooseBoxData(Inty(1),text), ChooseBoxData(Inty(12),text2))
这保留了index
类型信息(当然StringInt
超类型包装),以后可以使用单个元素的模式匹配轻松提取这些信息。
删除所有元素的包装器也很容易,但这会导致index
类型变得Any
这是我们所期望的,因为Any
是 Scala 类层次结构中String
和Int
的最低共同祖先。
编辑:使用无形状的解决方案
import shapeless._
import syntax.typeable._
case class ChooseBoxData[T](index : T, text : String)
val a = ChooseBoxData(1, "txt")
val b = ChooseBoxData("str", "txt")
val c = ChooseBoxData(true, "txt")
val list = List(a, b, c)
val `ChooseBoxData[Int]` = TypeCase[ChooseBoxData[Int]]
val `ChooseBoxData[String]` = TypeCase[ChooseBoxData[String]]
val res = list.map {
case `ChooseBoxData[Int]`(u) => u
case `ChooseBoxData[String]`(u) => u
case _ => None
}
//result
res: List[Product with Serializable] = List(ChooseBoxData(1,txt), ChooseBoxData(str,txt), None)
因此,它允许编译,但会用None
替换无效实例(如果需要,可以使用该实例来引发运行时错误),或者您可以直接过滤您想要使用的实例:
list.flatMap(x => x.cast[ChooseBoxData[Int]])
//results in:
List[ChooseBoxData[Int]] = List(ChooseBoxData(1,txt))
您可以在案例类之上构建额外的约束。
import language.implicitConversions
case class ChooseBoxData[T](index:T, text:String)
trait MySpecialConstraint[T] {
def get: ChooseBoxData[T]
}
implicit def liftWithMySpecialConstraintString(cbd: ChooseBoxData[String]) =
new MySpecialConstraint[String] {
def get = cbd
}
implicit def liftWithMySpecialConstraintInt(cbd: ChooseBoxData[Int]) =
new MySpecialConstraint[Int] {
def get = cbd
}
// Now we can just use this constraint for out list
val l1: List[MySpecialConstraint[_]] = List(ChooseBoxData("A1", "B1"), ChooseBoxData(2, "B2"))
为什么你不能这样做:
object solution extends App {
case class ChooseBoxData[T](index: T, text: String) extends GenericType[T]
trait GenericType[T] {
def getType(index: T, text: String): ChooseBoxData[T] = ChooseBoxData[T](index, text)
}
val specialList = List(
ChooseBoxData[String]("some string", "some string"),
ChooseBoxData[Int](12, "some string"),
ChooseBoxData[Boolean](true, "some string")
)
println(specialList)
}
//output: List(ChooseBoxData(some string,some string), ChooseBoxData(12,some string), ChooseBoxData(true,some string))