【问题标题】:Clojure Protocols vs Scala Structural TypesClojure 协议与 Scala 结构类型
【发布时间】:2011-05-29 07:57:25
【问题描述】:

看了 Clojure 1.2 中 Protocols 上的 the interview with Rich Hickey 之后,对 Clojure 知之甚少,我对 Clojure 协议有一些疑问:

  • 他们打算在 Scala 中做与Structural Types 相同的事情吗?协议相对于结构类型有什么好处(性能、灵活性、代码清晰度等)?它们是通过反射实现的吗?
  • 关于与 Scala 互操作性的问题:在 Scala 中可以使用协议代替结构类型吗?是否可以在 Scala 中扩展它们(如果“扩展”术语可以应用于协议)?

【问题讨论】:

  • 关于第一个项目符号,Haskell 中的类型类不是更接近 Scala 中的隐式吗?
  • 我的理解是,implicits 和 traits 是 Scala 对 Type Classes 的替代品(尤其是当涉及到现有功能的拉皮条时)。但是使用结构类型(和协议,我假设)可以实现的是将您不能(或不想)更改的现有类型的实例传递给只期望传递的对象具有特殊方法的 API致电:def call(c:{ def call():Unit }) = c.call()
  • 结构类型与类型类无关。
  • 我没说,它们是相关的。无论如何,现在已经从问题中删除了提及吸引太多注意力的类型类 :)
  • 你真的应该看看这个:vimeo.com/11236603。

标签: scala clojure protocols language-design structural-typing


【解决方案1】:

完全不相关。

Scala 是一种静态类型语言。 Clojure 是一种动态类型语言。这种差异从根本上塑造了它们。

结构类型是静态类型,周期。它们只是让编译器静态地证明一个对象将具有特定结构的一种方法(我在这里说证明,但转换会像往常一样导致虚假证明)。

Clojure 中的协议是一种创建动态调度的方法,它比反射或在地图中查找要快得多。从语义上讲,它们并没有真正扩展 Clojure 的功能,但在操作上它们比以前使用的机制要快得多。

Scala 特征更接近于协议,Java 接口也是如此,但同样存在静态与动态的问题。 Scala 特征必须在编译时与类关联,类似于 Java 接口。 Clojure 协议甚至可以由第三方在运行时添加到数据类型。

通过诸如包装器/代理模式或动态代理(http://download.oracle.com/javase/1.4.2/docs/guide/reflection/proxy.html)之类的机制,Java 和 Scala 中的 Clojure 协议之类的东西是可能的。但是这些会比 Clojure 协议笨拙得多,而且获得正确的对象身份也很棘手。

【讨论】:

  • 虽然 Clojure 协议可以在运行时扩展,但它们静态类型的,因为编译器将根据第一个参数的类型生成静态类型的函数调用 (或者如果类型未知/无法推断,则执行单个接口转换)。这是协议与多方法(完全动态等效)相比非常快的原因之一。
【解决方案2】:

Clojure 中协议的目的是以有效的方式解决表达式问题。

[见:Simple explanation of clojure protocols.]

Scala 对表达式问题的解决方案是隐式。因此,从语义上讲,that 最接近 Scala 中的 Clojure 协议。 (在 Haskell 中,它可能是 Typeclasses 或 Type Families。)

【讨论】:

    【解决方案3】:

    据我从introductory blogpost 了解到,闭包协议更接近于 Scala Traits,而不是结构类型(因此,不能用作它们的替代品,回答我的第二个问题):

    /* ----------------------- */
    /* --- Protocol definition */
    /* ----------------------- */
    
    (defprotocol Fly
      "A simple protocol for flying"
      (fly [this] "Method to fly"))
    
    /* --- In Scala */    
    trait Fly{
        def fly: String
    }
    
    /* --------------------------- */
    /* --- Protocol implementation */
    /* --------------------------- */
    
    (defrecord Bird [nom species]
      Fly
      (fly [this] (str (:nom this) " flies..."))
    
    /* --- In Scala */    
    case class Bird(nom: String, species: String) extends Fly{
        def fly = "%s flies..." format(nom)
    }
    
    /* --------------------- */
    /* --- Dynamic extension */
    /* --------------------- */
    
    (defprotocol Walk
      "A simple protocol to make birds walk"
      (walk [this] "Birds want to walk too!"))
    
    (extend-type Bird
      Walk
      (walk [this] (str (:nom this) " walks too..."))
    
    /* --- In Scala */    
    trait Walk{
        def walk = "Birds want to walk too!"
    }
    
    implicit def WalkingBird(bird: Bird) = new Walk{
        override def walk = "%s walks too..." format(bird.nom)
    }
    
    /* --------------- */
    /* --- Reification */
    /* --------------- */
    
    (def pig (reify
                    Fly (fly [_] "Swine flu...")
                    Walk (walk [_] "Pig-man walking...")))
    
    /* --- In Scala */    
    object pig extends Fly with Walk{
        def fly = "Swine flu..."
        override def walk = "Pig-man walking..."
    }
    

    【讨论】:

    • 动态扩展意味着使用扩展协议而不是定义一个新协议......例如如果你愿意,你可以将 Fly 扩展到 java.lang.String .....
    • 你可以使用隐式转换来达到同样的效果。这就是 Scala 扩展 Java 数组所做的工作。
    【解决方案4】:

    其他答案更能说明问题的其他部分,但是:

    它们是否通过 反思?

    否 - 协议编译为 JVM 接口。实现协议(reify、defrecord 等)的东西被编译为实现协议接口的 JVM 类,因此对协议函数的调用与标准 JVM 方法调用相同。

    这实际上是协议的动机之一——许多 Clo​​jure 的内部数据结构都是用 Java 编写的,出于速度原因,因为在纯 Clojure 中无法进行全速多态调度。协议提供了这一点。 Clojure 的源代码中仍然包含大量 Java,但现在都可以在 Clojure 中重写而不会损失性能。

    【讨论】:

    • 所以,虽然上面的答案强调这两件事是不同的,因为一个(在 Clojure 中)是动态类型,但另一个(在 Scala 中)是静态类型(James Iry),但仍然它们都对应于底层的 Java 接口。所以,事实上,它们是相似的,甚至在概念上也是相似的。
    • 顺便说一句,静态和动态类型之间的界限并不那么严格。 Java 被有意设计为,从一方面来看,它是一种像 C++ 这样的静态类型语言,但从另一方面来看,它看起来像 Smalltalk(动态类型)。 Java 与动态类型的 Smalltalk 有何相似之处?定义足够多的接口(为您使用的每种方法定义一个)并且您几乎就在那里:您是否可以向对象调用方法(=“发送特定消息”)与它是否从一个对象继承某些实现无关合适的超类。
    • Java 中的那种编程风格(许多接口来模拟 Smalltalk 的感觉)——虽然在概念上很好——但需要太多的输入。 Scala 语言通过使这些语言的结构简短易读,使这种 JVM 编程风格在表面上也很优雅!
    • @HassanSyed 取决于代码路径。在许多情况下,协议调用可以编译为单个invokevirtual JVM 指令,这是它所能获得的最快速度。
    猜你喜欢
    • 2012-05-29
    • 1970-01-01
    • 2018-04-27
    • 2011-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多