【发布时间】:2018-03-11 05:07:16
【问题描述】:
我最近看到很多关于 Clojure 中的谓词调度的讨论,我想知道这件事是否有什么问题。换句话说,什么是谓词调度,它与泛型函数、OOP 多态性和模式有何不同? 谢谢
【问题讨论】:
我最近看到很多关于 Clojure 中的谓词调度的讨论,我想知道这件事是否有什么问题。换句话说,什么是谓词调度,它与泛型函数、OOP 多态性和模式有何不同? 谢谢
【问题讨论】:
谓词调度包含泛型函数、OOP 多态性、模式匹配等。 Michael Ernst、Craig Kaplan 和 Craig Chambers 的 Predicate dispatching: A unified theory of dispatch 是一个很好的概述。摘自其摘要:
谓词分派通过允许任意谓词来控制方法的适用性并通过使用谓词之间的逻辑含义作为覆盖关系来概括以前的方法分派机制。选择用于处理消息发送的方法不仅取决于参数的类(如在普通的面向对象调度中),还取决于子组件的类、参数的状态以及对象之间的关系。
【讨论】:
已编辑: Clojure 多方法不是谓词调度。
在传统的面向对象编程中,多态性意味着您可以拥有一个方法的多个实现,而被调用的确切实现取决于您调用该方法的对象的类型 .这是类型调度。
Clojure multimethods 对此进行了扩展,以便任意函数可以决定调用哪个实现。在Clojure 形式(defmulti name f) 中,函数f 是dispatch 函数。
dispatch 函数可以是class,在这种情况下,您将返回键入 dispatch。但该功能可以是其他任何功能:计算调度值、在数据库中查找内容,甚至调用 Web 服务。
真正的谓词调度可能允许每个方法实现指定一个或多个调度函数(谓词)来决定何时应用该方法。这比多方法更通用,但实现起来更复杂。 Clojure 不支持它。
泛型函数是来自其他 Lisps 的术语。例如,Common Lisp 提供了generic functions,它可以根据类型以及一组受限的其他函数进行调度。
【讨论】:
谓词调度是一种根据函数参数的数量、“形状”和值对函数调用提供不同响应的方法。 Clojure 函数已经根据传递给函数的参数数量分派给不同的代码体:
(defn my-func
([a] (* a a))
([a b] (* a b)))
Clojure 多方法增加了根据分派函数的返回值分派到不同方法(可能定义在不同的命名空间中)的能力,该分派函数检查参数(可以包括它们的编号、类和值)并识别哪个方法给大家。正如 Stuart Sierra 回答的脚注中所指出的,多方法的创建者可以定义调度函数,并且通常不能对其进行修改。此外,程序员必须为一个函数手动设计一个超复杂的调度函数,该函数对 0 的整数执行一件事,对正整数执行另一件事;或者一件事用于一个或多个项目的列表,另一件事用于空列表。
谓词调度将(也许)提供一种语法来生成这个复杂的调度函数本身。例如,可以这样定义阶乘函数
(defmatch fact [0] 1)
(defmatch fact [n] (* n (fact (dec n))))
前面的代码响应调用
(fact 0)
后一个代码调用带有任何其他值的单个参数的调用。这将(在幕后)定义一个带有分派函数的多方法,该函数将零与其他值区分开来。
但后来我可以通过编码指定我想要一个地图的阶乘(也许)
(defmatch fact [x {}] (fact (:value x)))
并且代码可以(理论上)拦截将映射传递给事实的调用,将其他调用委托给原始调度函数......所有这些都在幕后。
【讨论】:
(fact [n]),但可能没有理由将它构建到谓词调度机制中(毕竟,不能保证你已经定义了一个纯函数)。跨度>
为了对比谓词调度和多方法,有点像你定义了多方法而不指定调度 fn:
(defmulti my-method)
而且,当你想扩展它时,你不需要指定一个 dispatch 值(因为没有 disaptch fn 来产生它)而是一个谓词:
(defmethod my-method (fn [a b] (and (vector? a) (vector? b)))
[a b]
(do something))
简单而强大。
问题是谓词可能重叠,而且您不想在每次调用时检查所有可能的谓词。这就是为什么实现会限制谓词的表达能力(类似于模式案例)以便能够智能地处理它们(检测歧义,创建快速决策树等)。
【讨论】: