clojure协议的简单解释 [英] Simple explanation of clojure protocols

查看:18
本文介绍了clojure协议的简单解释的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试了解 clojure 协议以及它们应该解决什么问题.有没有人对 clojure 协议的内容和原因有清楚的解释?

I'm trying to understand clojure protocols and what problem they are supposed to solve. Does anyone have a clear explanation of the whats and whys of clojure protocols?

推荐答案

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

The purpose of Protocols in Clojure is to solve the Expression Problem in an efficient manner.

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

So, what's the Expression Problem? It refers to the basic problem of extensibility: our programs manipulate data types using operations. As our programs evolve, we need to extend them with new data types and new operations. And particularly, we want to be able to add new operations which work with the existing data types, and we want to add new data types which work with the existing operations. And we want this to be true extension, i.e. we don't want to modify the existing program, we want to respect the existing abstractions, we want our extensions to be separate modules, in separate namespaces, separately compiled, separately deployed, separately type checked. We want them to be type-safe. [Note: not all of these make sense in all languages. But, for example, the goal to have them type-safe makes sense even in a language like Clojure. Just because we can't statically check type-safety doesn't mean that we want our code to randomly break, right?]

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

The Expression Problem is, how do you actually provide such extensibility in a language?

事实证明,对于过程和/或函数式编程的典型天真实现,添加新操作(过程、函数)非常容易,但添加新数据类型非常困难,因为基本上这些操作与数据一起工作使用某种大小写区分(switchcase、模式匹配)的类型,您需要向它们添加新的案例,即修改现有代码:

It turns out that for typical naive implementations of procedural and/or functional programming, it is very easy to add new operations (procedures, functions), but very hard to add new data types, since basically the operations work with the data types using some sort of case discrimination (switch, case, pattern matching) and you need to add new cases to them, i.e. modify existing code:

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)

现在,如果你想添加一个新的操作,比如类型检查,那很容易,但是如果你想添加一个新的节点类型,你必须修改所有操作中所有现有的模式匹配表达式.

Now, if you want to add a new operation, say, type-checking, that's easy, but if you want to add a new node type, you have to modify all the existing pattern matching expressions in all operations.

对于典型的原始 OO,您会遇到完全相反的问题:添加适用于现有操作的新数据类型很容易(通过继承或覆盖它们),但很难添加新操作,因为基本上意味着修改现有的类/对象.

And for typical naive OO, you have the exact opposite problem: it is easy to add new data types which work with the existing operations (either by inheriting or overriding them), but it is hard to add new operations, since that basically means modifying existing classes/objects.

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

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

Here, adding a new node type is easy, because you either inherit, override or implement all required operations, but adding a new operation is hard, because you need to add it either to all leaf classes or to a base class, thus modifying existing code.

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

Several languages have several constructs for solving the Expression Problem: Haskell has typeclasses, Scala has implicit arguments, Racket has Units, Go has Interfaces, CLOS and Clojure have Multimethods. There are also "solutions" which attempt to solve it, but fail in one way or another: Interfaces and Extension Methods in C# and Java, Monkeypatching in Ruby, Python, ECMAScript.

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

Note that Clojure actually already has a mechanism for solving the Expression Problem: Multimethods. The problem that OO has with the EP is that they bundle operations and types together. With Multimethods they are separate. The problem that FP has is that they bundle the operation and the case discrimination together. Again, with Multimethods they are separate.

所以,让我们比较一下协议和多方法,因为两者都做同样的事情.或者,换句话说:如果我们已经拥有多方法,为什么还要使用协议?

So, let's compare Protocols with Multimethods, since both do the same thing. Or, to put it another way: Why Protocols if we already have Multimethods?

Protocols 通过 Multimethods 提供的主要功能是 Grouping:您可以将多个函数组合在一起并说这 3 个函数 一起 形成 Protocol Foo".你不能用 Multimethods 做到这一点,它们总是独立存在.例如,你可以声明一个 Stack 协议由 both 一个 push 和一个 pop 函数组成 一起.

The main thing Protocols offer over Multimethods is Grouping: you can group multiple functions together and say "these 3 functions together form Protocol Foo". You cannot do that with Multimethods, they always stand on their own. For example, you could declare that a Stack Protocol consists of both a push and a pop function together.

那么,为什么不添加将 Multimethods 组合在一起的功能呢?这纯粹是出于务实的原因,这也是我在介绍性句子中使用高效"一词的原因:性能.

So, why not just add the capability to group Multimethods together? There's a purely pragmatic reason, and it is why I used the word "efficient" in my introductory sentence: performance.

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

Clojure is a hosted language. I.e. it is specifically designed to be run on top of another language's platform. And it turns out that pretty much any platform that you would like Clojure to run on (JVM, CLI, ECMAScript, Objective-C) has specialized high-performance support for dispatching solely on the type of the first argument. Clojure Multimethods OTOH dispatch on arbitrary properties of all arguments.

因此,协议限制您在 first 参数和 only 参数上分派 only (或作为 nil).

So, Protocols restrict you to dispatch only on the first argument and only on its type (or as a special case on nil).

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

This is not a limitation on the idea of Protocols per se, it is a pragmatic choice to get access to the performance optimizations of the underlying platform. In particular, it means that Protocols have a trivial mapping to JVM/CLI Interfaces, which makes them very fast. Fast enough, in fact, to be able to rewrite those parts of Clojure which are currently written in Java or C# in Clojure itself.

Clojure 实际上从 1.0 版开始就已经有了协议:例如,Seq 就是一个协议.但是在 1.2 之前,你不能用 Clojure 编写协议,你必须用宿主语言编写它们.

Clojure has actually already had Protocols since version 1.0: Seq is a Protocol, for example. But until 1.2, you couldn't write Protocols in Clojure, you had to write them in the host language.

这篇关于clojure协议的简单解释的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆