clojure协议的简单解释

clojure协议的简单解释,clojure,protocols,Clojure,Protocols,我试图理解clojure协议以及它们应该解决的问题。有人对clojure协议的内容和原因有明确的解释吗?我发现将协议视为在概念上类似于Java等面向对象语言中的“接口”最有帮助。协议定义了一组抽象的函数,这些函数可以用具体的方式为给定的对象实现 例如: (defprotocol my-protocol (foo [x])) 定义一个协议,其中一个函数名为“foo”,作用于一个参数“x” 然后,您可以创建实现该协议的数据结构,例如 (defrecord constant-foo [valu

我试图理解clojure协议以及它们应该解决的问题。有人对clojure协议的内容和原因有明确的解释吗?

我发现将协议视为在概念上类似于Java等面向对象语言中的“接口”最有帮助。协议定义了一组抽象的函数,这些函数可以用具体的方式为给定的对象实现

例如:

(defprotocol my-protocol 
  (foo [x]))
定义一个协议,其中一个函数名为“foo”,作用于一个参数“x”

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

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

(def a (constant-foo. 7))

(foo a)
=> 7
注意,这里实现协议的对象作为第一个参数
x
——有点像面向对象语言中隐含的“this”参数

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

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

(foo "Hello")
=> 5

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

那么,表达式有什么问题?它涉及可扩展性的基本问题:我们的程序使用操作操作数据类型。随着我们的程序的发展,我们需要用新的数据类型和新的操作来扩展它们。特别是,我们希望能够添加使用现有数据类型的新操作,并希望添加使用现有操作的新数据类型。我们希望这是真正的扩展,也就是说,我们不希望修改现有的程序,我们希望尊重现有的抽象,我们希望我们的扩展是单独的模块,在单独的名称空间中,单独编译,单独部署,单独检查类型。我们希望它们是类型安全的。[注意:并非所有这些在所有语言中都有意义。但是,例如,即使在Clojure这样的语言中,使它们具有类型安全性的目标也是有意义的。仅仅因为我们不能静态检查类型安全性并不意味着我们希望我们的代码随机中断,对吗?]

表达式的问题是,如何在语言中提供这种扩展性

事实证明,对于过程和/或函数编程的典型幼稚实现,添加新操作(过程、函数)非常容易,但添加新的数据类型却非常困难,因为基本上操作使用某种区分大小写的方法来处理数据类型(
开关
案例
,模式匹配),您需要向其添加新案例,即修改现有代码:

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)
现在,如果您想要添加一个新的操作,比如类型检查,这很容易,但是如果您想要添加一个新的节点类型,您必须修改所有操作中所有现有的模式匹配表达式

对于典型的朴素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
class AddOperator(左:节点,右:节点)
在这里,添加新节点类型很容易,因为您可以继承、重写或实现所有必需的操作,但添加新操作很难,因为您需要将其添加到所有叶类或基类,从而修改现有代码

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

请注意,Clojure实际上已经有了一种解决表达式问题的机制:多方法。OO与EP的问题在于,它们将操作和类型捆绑在一起。对于多方法,它们是分开的。FP的问题在于,它们将操作和区分大小写捆绑在一起。同样,对于多方法它们是分开的

那么,让我们比较一下协议和多方法,因为它们都做相同的事情。或者,换一种说法:如果我们已经有了多方法,为什么还要使用协议

协议在多方法上提供的主要功能是分组:您可以将多个函数分组在一起,并说“这三个函数一起构成协议
Foo
”。使用多方法无法做到这一点,因为它们总是独立存在。例如,您可以声明
堆栈
协议同时包含
推送
弹出
函数

那么,为什么不添加将多个方法组合在一起的功能呢?这是一个纯粹的语用原因,也是我在介绍性语句中使用“高效”一词的原因:性能

Clojure是一种托管语言。也就是说,它是专门设计用于在另一种语言的平台上运行的。事实证明,几乎所有您希望Clojure在其上运行的平台(JVM、CLI、ECMAScript、Objective-C)具有专门的高性能支持,仅对第一个参数的类型进行分派。Clojure Multimethods对所有参数的任意属性进行分派

因此,协议限制您仅对第一个参数和它的类型进行分派(或者作为
nil
的特例)

这并不是对协议本身思想的限制,而是访问底层平台的性能优化的一种务实选择。特别是,这意味着协议有一个微不足道的mappi