【发布时间】:2015-04-01 00:51:01
【问题描述】:
我有一个场景,我试图以允许类型推断从第一个参数推断第二个参数的类型的方式对案例类进行模式匹配。
当我的类型是不变的时,这可以正常工作,但是当其中一种类型是协变时,Scala(正确地)无法推断第二个参数的类型。我可以通过使用强制转换来解决这个问题,但我希望找到一个类型安全的解决方案。
这可能最好用代码来解释,所以我将从一个非常简化的示例开始。下面这个特定示例确实有效 - 下面详细说明了该问题。
// Schemas are an `open` type, so I don't know all possibilities ahead of time
trait Schema[T]
case object Contacts extends Schema[Contact]
case object Accounts extends Schema[Account]
case class Contact( firstName:String, lastName:String )
case class Account( name:String )
// I only really need records here. Schema does contain some
// needed metadata, but in this case is mostly just used
// as a type-tag so we know what type of record we have.
case class Changeset[T]( schema:Schema[T], records:Seq[T] )
def processChangeset[T]( cs: Changeset[T] ):Unit = {
val names = cs match {
// This is the important bit - inferring that
// `contacts` is a Seq[Contact]` and so forth.
case Changeset( Contacts, contacts ) => contacts.map(_.firstName)
case Changeset( Accounts, accounts ) => accounts.map(_.name)
case _ => Nil
}
}
processChangeset( Changeset( Accounts, Seq(Account("ACME"))))
在这个例子中,因为 Schema 的类型参数 T 是不变的。在解构“Changeset”类时,可以安全地推断出第二个参数是T——在这个例子中是联系人或帐户(Scala 编译器正确地做到了这一点)
但是在我正在使用的代码库中,这个参数是协变的,需要大量的工作来改变它。
trait Schema[+T]
这意味着,就类型安全而言,我们不能保证Changeset( Contacts, _ ) 的类型参数为“Contact”,因为我们也可以拥有Changeset[Any]( Contacts, Seq[Potato] )。
在运行时这个断言总是成立的,但编译器显然不能保证这一点。
我计划重构一些遗留代码以实现这一点,但这是一项相当大的工作。在我深入那个兔子洞之前,我想仔细检查一下没有更简单的方法可以做到这一点。
T 的类型将始终是没有子类的叶子,如果需要,我可以给 T 一个类型边界。考虑到这些限制,一种语言似乎可以在模式匹配时正确推断类型,但我不确定 Scala 是否专门可以做到这一点。
【问题讨论】:
标签: scala generics pattern-matching covariance type-inference