Scala中的子类型:什么是“type X <: Y”?

9
有人能解释一下以下代码中的subtype(<:)吗?为什么可以这样使用?我们什么时候使用它?谢谢。
trait SwingApi {

    type ValueChanged <: Event

    val ValueChanged: {
       def unapply(x: Event): Option[TextField]
    }

    type ButtonClicked <: Event

    val ButtonClicked: {
        def unapply(x: Event): Option[Button]
    }

    type TextField <: {
        def text: String
        def subscribe(r: Reaction): Unit
        def unsubscribe(r: Reaction): Unit
    }

    type Button <: {
        def subscribe(r: Reaction): Unit
        def unsubscribe(r: Reaction): Unit
    }

}

2
尽管这个问题看起来很有趣,但你实际上所询问的并不清楚。你是在特别询问如何在结构类型中使用<:,还是只是一般地了解<:的用法? - DaoWen
2个回答

12

我知道那段代码! :)

所以让我们确保你理解 <: 的意思,以防万一。 A <: B 意味着 A 必须是 B 的子类型,换句话说,A 的每个实例也将是 B 的实例(但反之则不然)。

例如,我们知道每个Java类都是<: Object(如 String <: Object)。

接下来是为什么 type ValueChanged <: Event。这通常在蛋糕模式中找到,但我会跳过对此的解释(课程提到了蛋糕模式,并提供了一个链接,如果需要可以查看)。

这意味着对于任何扩展 SwingApi 的内容,类型 ValueChanged 必须是 Event 的子类型。这使得可以在声明为 ValueChanged 类型的任何内容上调用 Event 方法,而无需预先知道确切的类型是什么。

这与下一个用法类似:

type TextField <: {
    def text: String
    def subscribe(r: Reaction): Unit
    def unsubscribe(r: Reaction): Unit
}

我们在这里声明 TextField 应该具有那些方法,因此当我们获取到类型为 TextField 的东西(比如由 ValueChanged 提取器返回的东西)时,我们可以在其上调用这些方法。我们可以编写如下代码:

trait MyStuff extends SwingApi {
  def subscribeTo(event: ValueChanged) = event match {
    case ValueChanged(textField) => textField.subscribe(myReaction)
  }

  def myReaction: Reaction
}

此时,无论是SwingApi还是MyStuff都不知道将用于ValueChangedTextField的类型,但是它们可以在正常代码中使用。

关于type声明,一个常被忽视的有趣事实是它们可以被类覆盖。也就是说,我可以这样写:

class SwingImpl extends SwingApi {
  class TextField {
    def text: String = ???
    def subscribe(r: Reaction): Unit = ???
    def unsubscribe(r: Reaction): Unit = ???
  }

  // etc
}

最后,您可能会想知道这有什么用处。我来举一个例子。自然地,您希望生产代码在屏幕上显示图形元素等内容,也许您可以编写一个单独的类,在Web服务器上实现它。但是,我认为本课程充分利用了这一点,您可以将实现这些组件的类编写为测试类,以验证与这些组件的交互是否正确。

也就是说,您可以拥有扩展SwingApi并在桌面上显示其内容的SwingImpl,还可以拥有扩展SwingApi但只用于验证操作是否正确的SwingTest


谢谢您的回答!我还有一点困惑。这里的ValueChanged只是一个带有额外方法unapply()的Event子类型吗?为什么在SwingImpl类中,它可以被赋值为类型ValueChanged = scala.swing.event.ValueChanged?TextField是否是“匿名特质”({ def text: String def subscribe(r: Reaction): Unit def unsubscribe(r: Reaction): Unit })的子类型?谢谢。 - yuefengz
@Fake Scala有两个命名空间:类型和值。声明type ValueChanged <: Event在类型命名空间上创建了一个ValueChanged标识符。声明val ValueChanged: {...}在_value_命名空间上创建了一个ValueChanged标识符,因此它是完全独立的。value ValueChanged具有类型_"定义一个unapply方法,该方法接受一个Event参数并返回一个Option[TextField]",而_type ValueChangedEvent的子类型。除了在不同的命名空间中具有相同的名称之外,这两者没有关联。 - Daniel C. Sobral
下一个问题是,它可以被赋值,因为类型可以被赋值,只要它们遵守继承约束。由于scala.swing.event.ValueChangedEvent的子类型,因此该类型分配是有效的。 TextField是具有该定义的_结构类型_的子类型(或者,作为替代方案,是类型改进的子类型)。 - Daniel C. Sobral
两个命名空间。有趣!我大部分都明白了,但还需要时间来消化。非常感谢! - yuefengz

1

通过抽象类型成员ValueChangedButtonClickedtrait SwingApi本身是不可实例化的(所有trait都是如此,但如果它们被完全实现,则可以轻松地将其转换为可以实例化的具体类)。

这些约束条件表明,SwingApi的可实例化子类型必须将ValueChangedButtonClicked定义为Event的子类型。

类型别名TextFieldButton被限制为结构类型(它们不需要特定的子类关系,而只需以指定类型提供指定的成员)。

为什么这样做?答案很简单:泛化。这对trait SwingApi实现者施加了最小的约束,使其能够被需要SwingApi的代码使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接