【问题标题】:Structural Type Dispatch in ScalaScala中的结构类型调度
【发布时间】:2009-09-16 20:04:58
【问题描述】:

我试图更好地掌握结构类型调度。例如,假设我有一个带有 summary 方法的可迭代对象来计算平均值。所以o.summary() 给出了列表的平均值。我可能想使用结构类型调度来启用summary(o)

  1. 是否有一套关于o.summary()summary(o) 的最佳实践?
  2. 如果我有方法summary(o: ObjectType)summary(o: { def summary: Double}),scala 如何解析summary(o)
  3. 结构化类型分派与多方法或泛型函数有何不同?

Michael Galpin 给了following discription 关于structural type 的调度:


结构类型是 Scala 版本的“响应式”编程,如在许多动态语言中所见。很喜欢

def sayName ( x : { def name:String }){
    println(x.name)
}

然后任何对象的方法名为 name 且不带参数并返回一个字符串,都可以传递给 sayName:

case class Person(name:String)
val dean = Person("Dean")
sayName(dean) // Dean

【问题讨论】:

    标签: scala


    【解决方案1】:

    -- 1. 在您的示例中,我不会使用summary(o) 版本,因为这不是一种非常面向对象的编程风格。当调用o.summary 时(你可以去掉括号,因为它没有副作用),你要求osummary 属性。当调用summary(o) 时,您将o 传递给计算o 的摘要的方法。我相信第一种方法更好:)。

    我没有过多使用结构类型分派,但我认为它最适合(在大型系统中)仅因为一个方法想要一个定义了某个方法的类型而必须编写接口的情况.有时创建该接口并强制客户端实现它可能会很尴尬。有时您想使用在另一个 API 中定义的客户端,该客户端符合您的接口但没有显式实现它。因此,在我看来,结构类型分派是隐式制作适配器模式的好方法(节省样板文件,耶!)。

    -- 2. 显然,如果您调用 summary(o) 并且 o 属于 ObjectType,则 summary(o: ObjectType) 会被调用(这确实有意义)。如果您调用summary(bar),其中bar 不属于ObjectType,可能会发生两件事。如果bar 具有正确签名和名称的summary() 方法,则调用编译,否则,调用不会编译。

    例子:

    scala> case class ObjectType(summary: Double)
    defined class ObjectType
    
    scala> val o = ObjectType(1.2)
    o: ObjectType = ObjectType(1.2)
    
    scala> object Test {
         | def summary(o: ObjectType)  { println("1") }
         | def summary(o: { def summary: Double}) { println("2")}
         | }
    defined module Test
    
    scala> Test.summary(o)
    1
    

    很遗憾,由于类型擦除,以下内容无法编译:

    scala> object Test{
         | def foo(a: {def a: Int}) { println("A") }
         | def foo(b: {def b: Int}) { println("B") }
         | }
    :6: error: double definition:
    method foo:(AnyRef{def b(): Int})Unit and
    method foo:(AnyRef{def a(): Int})Unit at line 5
    have same type after erasure: (java.lang.Object)Unit
           def foo(b: {def b: Int}) { println("B") }
    

    -- 3. 从某种意义上说,结构类型的调度比泛型方法更动态,并且服务于不同的目的。在通用方法中,您可以说:我想要任何类型的东西;湾。我想要某种类型的东西,它是 A 的子类型; C。我将采用 B 的超类型; d。我将采用隐式转换为 C 类型的东西。所有这些都比“我想要一个具有正确签名的方法 foo 的类型”要严格得多。此外,结构类型调度确实使用反射,因为它们是通过类型擦除实现的。

    我对multimethods了解不多,但是看wikipedia article,似乎Scala中的multimethods可以使用模式匹配来实现。示例:

    def collide(a: Collider, b: Collider) = (a, b) match {
        case (asteroid: Asteroid, spaceship: Spaceship) => // ...
        case (asteroid1: Asteroid, asteroid2: Asteroid) => // ...
    ... 
    

    同样,您可以使用结构类型调度 - def collide(a: {def processCollision()}),但这取决于设计决策(我将在此示例中创建一个接口)。

    -- Flaviu Cipcigan

    【讨论】:

    • 谢谢,这很有帮助。关于你的最后一点,我相信多方法试图避免 case/switch 类型语句,因为所有使用 collide 的类型都必须被 api 设计者知道。
    【解决方案2】:

    结构数据类型并不是那么有用。这并不是说它们没用,但它们绝对是一个小众的东西。

    例如,您可能想为某事物的“size”编写一个通用测试用例。你可以这样做:

    def hasSize(o: { def size: Int }, s: Int): Boolean = {
      o.size == s
    }
    

    这可以用于任何实现“size”方法的对象,无论其类层次结构如何。

    现在,它们不是结构类型的调度。它们与调度无关,但与类型定义有关。

    Scala 始终是一种面向对象的语言。您必须调用对象的方法。函数调用实际上是“apply”方法调用。像“println”这样的东西只是导入作用域的对象的成员。

    【讨论】:

      【解决方案3】:

      我想你问过 Scala 对结构类型的调用做了什么。它使用反射。例如,考虑

      def repeat(x: { def quack(): Unit }, n: Int) {
         for (i <- 1 to n) x.quack()
      }
      

      调用x.quack() 被编译为quack 方法的查找,然后是调用,两者都使用Java 反射。 (您可以通过使用 javap 查看字节码来验证这一点。寻找一个具有有趣名称的类,例如 Example$$anonfun$repeat$1。)

      如果你仔细想想,这并不奇怪。由于没有通用接口或超类,因此无法进行常规方法调用。

      因此,其他受访者绝对正确,除非您必须这样做,否则您不想这样做。成本非常高。

      【讨论】:

        猜你喜欢
        • 2019-02-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-29
        • 2011-03-29
        相关资源
        最近更新 更多