我是否正确理解在(大多数?一些?)多种调度语言中,每个方法都会在程序执行的某个时间点添加到函数中。
然后我可以得出结论,作为一个特性的多次调度强制函数是可变的吗?
是否有多种调度语言,其中所有方法都附加到一个(通用)函数(在加载时?),所以不可能在不同的时间点看到不同状态的函数?
我是否正确理解在(大多数?一些?)多种调度语言中,每个方法都会在程序执行的某个时间点添加到函数中。
然后我可以得出结论,作为一个特性的多次调度强制函数是可变的吗?
是否有多种调度语言,其中所有方法都附加到一个(通用)函数(在加载时?),所以不可能在不同的时间点看到不同状态的函数?
在程序执行的某个时间点。
在 Common Lisp 中,方法在执行方法定义时被添加/替换——对于编译系统,这通常是在编译代码的加载时——不一定在程序执行期间。
请记住,Common Lisp 有一个对象系统(CLOS,Common Lisp 对象系统),它是由它的行为定义的。它与语言或语言扩展略有不同。
Common Lisp 允许对象系统的运行时修改。例如还添加/删除/替换方法。
Common Lisp 也可以将多个适用的方法组合成一个有效的方法,然后执行。典型例子:所有适用:before
的方法和最具体的适用的主要方法将组合成一个有效的方法。
在某些实现中存在对 CLOS 的扩展,它们密封通用功能以防止更改。
有关对象系统概念的更详细介绍,请参见:Richard P. Gabriel的《编程语言革命的结构》 。
在 Common Lisp 中,您可以从规范中阅读以下内容:
评估表单时
defgeneric
,将采取以下三种操作之一(由于ensure-generic-function
):
- 如果给定名称的通用函数已经存在,则修改现有的通用函数对象。添加当前表单指定的方法,并删除
defgeneric
现有通用函数中由先前表单定义的任何方法。defgeneric
当前defgeneric
表单添加的方法可能会替换由defmethod
、defclass
、define-condition
或定义的方法defstruct
。通用函数中的其他方法不会受到影响或被替换。- 如果给定名称命名了普通函数、宏或特殊运算符,则会发出错误信号。
defgeneric
否则,将使用表单中方法定义指定的方法创建通用函数。
评估方法定义表单时,会创建一个方法对象并执行以下四种操作之一:
- 如果给定名称的泛型函数已经存在,并且如果已经存在与新的参数专用器和限定符一致的方法对象,则新方法对象将替换旧方法对象。有关在参数专用器和限定符上与另一种方法一致的定义,请参阅第 7.6.3 节(关于参数专用器和限定符的协议)。
- 如果给定名称的泛型函数已经存在,并且没有与新方法对象一致的参数专用器和限定符,则修改现有的泛型函数对象以包含新方法对象。
- 如果给定名称命名了普通函数、宏或特殊运算符,则会发出错误信号。
- 否则,将使用方法定义形式指定的方法创建通用函数。
如果 function-name 指定了一个具有不同参数值的泛型函数
:lambda-list
,并且新值与所有现有方法的 lambda 列表一致或没有方法,则更改该值;否则会发出错误信号。如果 function-name 指定了一个具有不同参数值的泛型函数,
:generic-function-class
并且如果新的泛型函数类与旧的兼容,change-class
则调用以更改泛型函数的类;否则会发出错误信号。如果 function-name 指定了一个具有不同参数值的通用函数,则
:method-class
该值会更改,但任何现有方法都不会更改。
如您所见,泛型函数在defmethod
定义之间,甚至在defgeneric
定义之间保留其标识。泛型函数在 Common Lisp 中是可变的。
在 Julia 中,您可以从文档中阅读以下内容:
要定义具有多种方法的函数,只需使用不同数量和类型的参数多次定义该函数。函数的第一个方法定义创建函数对象,随后的方法定义将新方法添加到现有函数对象。
如您所见,函数对象在 Julia 中是可变的。
这并没有说明所有其他多种调度语言。你现在可以发明一种多分派语言,只是为了展示你可以做到不变性,例如,添加方法将返回一个与前一个函数类似但具有添加方法的新函数。或者一种在编译时静态生成函数的语言,这样您就不能在运行时以任何方式更改它,甚至不能添加或删除方法。
从优秀的“ Getting started with Julia ”一书中转述,其中有一个很好的部分(强调我的):
我们已经看到函数本质上被定义为泛型,也就是说,它们可以用于不同类型的参数。每次使用新类型的参数调用该函数时,编译器都会生成一个单独的函数版本。用于特定参数类型组合的函数的具体版本在 Julia中称为方法。要为函数定义新方法(也称为重载),只需使用相同的函数名称但不同的签名,即使用不同的参数类型。
所有方法的列表存储在函数本身的虚拟方法表 (vtable) 中;方法不属于特定类型。当一个函数被调用时,Julia 将在运行时在该 vtable 中进行查找,以根据其所有参数的类型找到它应该调用的具体方法;这是 Julia 的多重分派机制,Python、C++ 或 Fortran 都没有实现。它允许开放扩展,普通的面向对象代码会迫使您更改类或子类现有类,从而更改您的库。请注意,多重分派只考虑位置参数,而不考虑关键字参数。
对于这些不同方法中的每一种,都会生成专门针对处理器指令集的低级代码。与面向对象 (OO) 语言相比,vtable 存储在函数中,而不是类型(或类)中。在 OO 语言中,在单个对象 object.method() 上调用方法,通常称为单调度。在 Julia 中,可以说一个函数属于多种类型,或者一个函数针对不同的类型进行了专门化或重载。Julia 能够将读起来像高级动态语言的代码编译成几乎完全像 C 语言一样执行的机器代码的能力源于其进行多重分派的能力。
所以,我理解这一点的方式(我可能错了)是:
methods()
(但是,如果您在该函数名称上运行,这不会显示为显式方法)我不会说这会使函数可变,但这是一个完全不同的问题。您可以使用isimmutable()
函数“句柄”上的函数来确认它们是不可变的。
*我知道模块可以预编译,但我不完全确定这些即时编译的版本是否以任何形式保存在会话之间——欢迎评论:)
动态性可以成为您应用程序中的真正资产,即使仅用于调试。试图阻止函数稍后被更新、重新定义等可能有点短视。但是如果你确定你想要静态调度,你可以定义你自己的泛型函数类,这要归功于 MOP,元对象协议,它不是标准的一部分,但仍然得到很大的支持。这就是Inlined-Generic-Function库所提供的(这是可能的,因为 CLOS 对扩展开放)。
在 Dylan 中,方法通常在编译时添加到泛型函数中,但它们也可以在运行时添加(通过add-method或remove-method)。 但是,通用函数可能是密封的,这会阻止定义 gf 的库以外的库添加方法。因此,要回答您的问题,在 Dylan 中,泛型函数在定义库中始终是可变的,但它们可能对其他库不可变。