功能在多个调度系统中是否可变?

时间:2016-11-18 09:01:19

标签: common-lisp julia multimethod multiple-dispatch dylan

我是否正确理解在(大多数?某些?)多种调度语言中,每种方法都会在程序执行的某个时间点添加到函数

我可以得出结论,多个调度作为一个特征强制函数是可变的吗?

是否存在多种调度语言,其中所有方法一起附加到(通用)函数(在加载时?),因此不可能在不同的时间点看到不同状态的函数?

5 个答案:

答案 0 :(得分:5)

  

在程序执行的某个时间点。

在Common Lisp中,方法在执行方法定义时被添加/替换 - 对于已编译的系统,这通常是在编译代码的加载时 - 不一定是在程序执行期间。

请记住,Common Lisp有一个对象系统(CLOS,Common Lisp对象系统),它由其行为定义。它与语言语言扩展略有不同。

Common Lisp允许运行时修改对象系统。例如,还添加/删除/替换方法。

Common Lisp还可以将多个适用的方法组合到有效方法中,然后执行该方法。典型示例:所有适用的:before方法和最具体的适用主要方法将合并为一个有效方法

在某些实现中存在CLOS的扩展,密封针对更改的通用函数。

有关对象系统概念的更长时间的处理,请参阅Richard P. Gabriel的The Structure of a Programming Language Revolution

答案 1 :(得分:3)

在Common Lisp中,您可以从规范中阅读以下内容:

7.6.1 Introduction to Generic Functions

  

评估defgeneric表单时,会执行以下三项操作之一(由于ensure-generic-function):

     
      
  • 如果已存在给定名称的通用函数,则修改现有的通用函数对象。添加当前defgeneric表单指定的方法,并删除现有通用函数中由先前defgeneric表单定义的任何方法。当前defgeneric表单添加的方法可能会替换defmethoddefclassdefine-conditiondefstruct定义的方法。通用功能中没有其他方法受到影响或替换。
  •   
  • 如果给定的名称命名普通函数,宏或特殊运算符,则会发出错误信号。
  •   
  • 否则,使用defgeneric表单中方法定义指定的方法创建泛型函数。
  •   

7.6.2 Introduction to Methods

  

评估方法定义表单时,会创建一个方法对象,并采取以下四种操作之一:

     
      
  • 如果给定名称的泛型函数已经存在,并且如果已存在的方法对象与参数特化器和限定符上的新对象一致,则新方法对象将替换旧方法对象。有关一个方法的定义与参数特化器和限定符的另一个方法一致,请参见第7.6.3节(参数专用器和限定符的协议)。
  •   
  • 如果给定名称的泛型函数已经存在,并且如果没有方法对象与参数specializers和qualifiers的新函数一致,则修改现有的泛型函数对象以包含新的方法对象。
  •   
  • 如果给定的名称命名普通函数,宏或特殊运算符,则会发出错误信号。
  •   
  • 否则,使用方法定义表单指定的方法创建泛型函数。
  •   

ensure-generic-function的定义:

  

如果function-name指定了一个对:lambda-list参数具有不同值的泛型函数,并且新值与所有现有方法的lambda列表一致,或者没有方法,则更改该值;否则会发出错误信号。

     

如果function-name指定了一个对:generic-function-class参数具有不同值的泛型函数,并且新泛型函数类与旧函数类兼容,则调用change-class来更改类的:method-class通用功能;否则会发出错误信号。

     

如果function-name指定了defmethod参数的值不同的泛型函数,则值会更改,但不会更改任何现有方法。

您还有add-methodremove-method

如您所见,通用函数在defgeneric定义之间,甚至在{{1}}定义之间保留了它们的标识。通用函数在Common Lisp中是可变的。

在Julia中,您可以从文档中阅读以下内容:

Defining Methods

  

要定义具有多个方法的函数,可以多次定义函数,使用不同数量和类型的参数。函数的第一个方法定义创建函数对象,后续方法定义将新方法添加到现有函数对象。

如您所见,Julia中的函数对象是可变的。

这并未说明所有其他多种派遣语言。你现在可以发明一种多种派遣语言,只是为了表明你可以通过不变性做到这一点,例如:添加方法将返回一个类似于前一个函数的新函数,但使用添加的方法。或者是在编译时静态生成函数的语言,这样您就无法以任何方式在运行时更改它,甚至不添加或删除方法。

答案 2 :(得分:2)

从优秀的“Getting started with Julia”一书中诠释,这本书有一个很好的部分(强调我的):

  

我们已经看到函数本质上被定义为泛型,也就是说,它们可以用于不同类型的参数。 每次使用新类型的参数调用时,编译器都会生成一个单独的函数版本。用于特定参数类型组合的函数的具体版本在Julia中称为方法要为函数定义新方法(也称为重载),只需使用相同的函数名称,但使用不同的签名,即使用不同的参数类型

     

所有方法的列表都存储在函数本身的虚方法表(vtable)中;方法不属于特定类型。 调用函数时,Julia将在运行时在该vtable中执行查找,以根据其所有参数的类型查找应调用的具体方法;这是Julia的多重调度机制,Python,C ++或Fortran都没有实现。它允许开放式扩展,正常的面向对象代码会强制您更改现有类的类或子类,从而更改库。请注意,多个分派只考虑位置参数,而不考虑关键字参数。

     

对于这些不同的方法中的每一种,都会生成专门的低级代码,目标是处理器的指令集。 与面向对象(OO)语言相比,vtable存储在函数中,而不是存储在类型(或类)中。在OO语言中,在单个对象object.method()上调用方法,该对象通常称为单个分派。在Julia中,可以说函数属于多个类型,或者函数是针对不同类型的专用或重载。 Julia将编写为高级动态语言的代码编译成几乎完全像C一样的机器代码的能力来源于它能够进行多次调度。

所以,我理解这一点的方式(我可能错了)是:

  • 通用功能需要在会话中定义才能使用
  • 在函数的多调度查找表中定义明确定义的具体参数方法。
  • 每当使用不存在显式定义的方法的特定参数调用函数时,将编译这些参数的具体版本并将其添加到vtable中。 (但是,如果在该函数名称上运行methods(),则不会显示为显式方法)
  • 这样一个函数的第一次调用会导致一些编译开销;但是,后续调用将使用现有的编译版本*。

我不会说这会使函数 mutable ,但这是一个完全不同的问题。您可以使用函数'handle'上的isimmutable()函数确认它们是不可变的。

*我知道模块可以预编译,但我不完全确定这些正在运行的编译版本是否以任何形式保存在会话之间 - 欢迎评论:)< / sub>

答案 3 :(得分:2)

如果仅用于调试,动态性可以是应用程序中的真正资产。试图阻止函数稍后更新,重新定义等可能有点短视。但是如果您确定需要静态调度,那么您可以定义自己的泛型函数类,这要归功于MOP,即元对象协议,它不是标准的一部分,但仍然受到很大支持。这就是Inlined-Generic-Function库提供的内容(这是可能的,因为CLOS对扩展是开放的。)

答案 4 :(得分:1)

在Dylan中,方法通常在编译时添加到泛型函数,但也可以在运行时添加(通过add-methodremove-method)。 但是,一个通用函数可能是sealed,它阻止了除g.f之外的其他库。由添加方法定义。因此,为回答您的问题,在Dylan中,泛型函数在定义库中始终是可变的,但对于其他库则可能不可变。