在Scala中将协变类型视为不变量?

时间:2015-02-02 05:25:51

标签: scala generics pattern-matching covariance type-inference

我有一个场景,我试图以一种允许类型推断从第一个参数推断出第二个参数类型的方式对一个案例类进行模式匹配。

当我的类型是不变的时,这可以正常工作,但是当其中一个类型是协变时,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是不变的。在解构"变更集"类,可以安全地推断第二个参数是T - 在这个例子中是联系人或帐户(并且Scala编译器正确地执行了这个操作)

然而,在我使用的代码库中,这个参数是协变的,需要做大量工作才能改变它。

trait Schema[+T]

这就意味着,就类型安全而言,我们无法保证Changeset( Contacts, _ )的类型参数为“联系人”,因为我们也可以Changeset[Any]( Contacts, Seq[Potato] )

在运行时,这个断言总是成立,但编译器显然不能保证这一点。

我计划重构一些遗留代码以使其成为可能,但这是一项相当大的工作。在我深入那个兔子洞之前,我想仔细检查一下,这样做的方法并不简单。

T的类型将始终是没有子类的叶子,并且如果需要,我能够为T提供类型边界。鉴于这些限制,似乎一种语言可以在模式匹配时正确地推断出类型,但我不确定Scala是否能够专门做到这一点。

1 个答案:

答案 0 :(得分:3)

你可以引入另一个特征,一个不变的特征:

trait LeafSchema[T] extends Schema[T]

ContactAccount扩展。然后,您坚持在需要安全匹配的任何内容中使用LeafSchema,并使用Schema上的匹配进行操作。

这是否真的是明智的,或类型系统中的漏洞,我不确定。我倾向于将其视为一个洞。但你可以做到,在你的情况下它应该是安全的。