对clojure协议的简单解释

Zub*_*air 126 protocols clojure

我正在尝试理解clojure协议以及它们应该解决的问题.有没有人对clojure协议的最新情况有什么明确的解释?

Jör*_*tag 271

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

那么,表达问题是什么?它指的是可扩展性的基本问题:我们的程序使用操作来操纵数据类型.随着我们的程序的发展,我们需要使用新的数据类型和新操作来扩展它们.特别是,我们希望能够添加与现有数据类型一起使用的新操作,并且我们希望添加与现有操作一起使用的新数据类型.而且我们希望这是真实的扩展,即我们不希望修改现有的程序,我们要尊重现有的抽象,我们希望我们的扩展是独立的模块,不同的命名空间,单独编译,单独部署,独立型检查.我们希望它们是类型安全的.[注意:并非所有这些都适用于所有语言.但是,例如,即使在像Clojure这样的语言中,使它们具有类型安全性的目标也是有意义的.仅仅因为我们无法静态检查类型安全并不意味着我们希望我们的代码随机中断,对吧?

表达式问题是,你如何在语言中实际提供这样的可扩展性?

事实证明,对于程序和/或函数编程的典型天真实现,添加新操作(过程,函数)非常容易,但很难添加新数据类型,因为基本上操作使用一些数据类型一种案例歧视(switch,case模式匹配),您需要向它们添加新案例,即修改现有代码:

func print(node):
  case node of:
    AddOperator => print(node.left) + '+' + print(node.right)
    NotOperator => '!' + print(node)

func eval(node):
  case node of:
    AddOperator => eval(node.left) + eval(node.right)
    NotOperator => !eval(node)
Run Code Online (Sandbox Code Playgroud)

现在,如果要添加新操作(例如,类型检查),这很容易,但如果要添加新节点类型,则必须在所有操作中修改所有现有模式匹配表达式.

对于典型的天真OO,你有一个完全相反的问题:很容易添加与现有操作一起使用的新数据类型(通过继承或覆盖它们),但很难添加新操作,因为这基本上意味着修改现有的类/对象.

class AddOperator(left: Node, right: Node) < Node:
  meth print:
    left.print + '+' + right.print

  meth eval
    left.eval + right.eval

class NotOperator(expr: Node) < Node:
  meth print:
    '!' + expr.print

  meth eval
    !expr.eval
Run Code Online (Sandbox Code Playgroud)

在这里,添加新的节点类型很容易,因为您要么继承,覆盖或实现所有必需的操作,但添加新操作很难,因为您需要将它添加到所有叶类或基类,从而修改现有的码.

有几种语言有几种解决表达式问题的结构:Haskell有类型类,Scala有隐式参数,Racket有单位,Go有接口,CLOS和Clojure有多种方法.还有"解决方案" 试图解决它,但以某种方式失败:C#和Java中的接口和扩展方法,Ruby中的Monkeypatching,Python,ECMAScript.

请注意,Clojure实际上已经有了解决表达式问题的机制:Multimethods.OO与EP的问题在于它们将操作和类型捆绑在一起.使用Multimethods它们是分开的.FP具有的问题是它们将操作和案例辨别捆绑在一起.同样,使用Multimethods它们是分开的.

所以,让我们将Protocols与Multimethods进行比较,因为两者都做同样的事情.或者,换句话说:如果我们已经拥有 Multimethods,为什么要使用Protocols ?

Protocols对Multimethods提供的主要功能是分组:您可以将多个功能组合在一起,并将"这3个功能组合在一起形成协议Foo".你不能用Multimethods做到这一点,他们总是独立存在.例如,你可以声明一个Stack协议由双方pushpop功能一起.

那么,为什么不添加将Multimethods组合在一起的功能呢?这是一个纯粹实用的理由,这就是为什么我在我的介绍性句子中使用"高效"这个词:表现.

Clojure是一种托管语言.即它专门设计为在另一种语言平台上运行.事实证明,您希望Clojure运行的任何平台(JVM,CLI,ECMAScript,Objective-C)都具有专门的高性能支持,针对第一个参数的类型进行调度.Clojure的多方法OTOH派遣的任意属性所有参数.

因此,协议限制您在第一个参数上调度,而在其类型上调度(或作为特殊情况nil).

这不是对协议本身的限制,它是一种实用的选择,可以访问底层平台的性能优化.特别是,这意味着协议具有对JVM/CLI接口的简单映射,这使得它们非常快.事实上,足够快,能够重写Clojure中那些当前用Clojure本身用Java或C#编写的部分.

事实上,Clojure已经拥有协议,因为版本1.0:Seq例如,它是一个协议.但是直到1.2,你无法在Clojure中编写Protocols,你必须用宿主语言编写它们.

  • 关于表达式问题和clojure协议的优秀文章 - http://www.ibm.com/developerworks/library/j-clojure-protocols/ (3认同)

mik*_*era 64

我认为将协议视为概念上类似于面向对象语言(如Java)中的"接口"是最有帮助的.协议定义了一组抽象函数,可以以给定对象的具体方式实现.

一个例子:

(defprotocol my-protocol 
  (foo [x]))
Run Code Online (Sandbox Code Playgroud)

使用一个名为"foo"的函数定义一个协议,该函数作用于一个参数"x".

然后,您可以创建实现协议的数据结构,例如

(defrecord constant-foo [value]  
  my-protocol
    (foo [x] value))

(def a (constant-foo. 7))

(foo a)
=> 7
Run Code Online (Sandbox Code Playgroud)

请注意,这里实现协议的对象作为第一个参数传递x- 有点像面向对象语言中的隐式"this"参数.

协议的一个非常强大和有用的功能是,即使对象最初不是为支持协议而设计的,也可以将它们扩展到对象.例如,如果您愿意,可以将上面的协议扩展到java.lang.String类:

(extend-protocol my-protocol
  java.lang.String
    (foo [x] (.length x)))

(foo "Hello")
=> 5
Run Code Online (Sandbox Code Playgroud)

  • &gt; 就像面向对象语言中隐含的“this”参数一样,我注意到传递给协议函数的 var 在 Clojure 代码中也经常被称为“this”。 (2认同)