在 Common Lisp 中更改方法分派

Changing method dispatch in Common Lisp

我正在尝试使用 Common Lisp 的 CLOS 模拟类似于 Haskell 的类型classes。也就是说,我希望能够在对象的 "typeclasses" 而不是它的超 class 上分派一个方法。

我为 classes 定义了一个元class,它具有并实现类型classes(它们只是其他 classes)。那些 classes(那些实现 typeclasses 的)有一个插槽,其中包含它们实现的 typeclasses 的列表。
我希望能够为类型 class 定义方法,然后能够在 class 实现该类型 class 的对象上分派该方法。我希望能够动态地添加和删除 typeclasses。

我想我可能可以通过更改方法分派算法来做到这一点,尽管这看起来不太简单。

有人对 CLOS 和 MOP 感到满意,可以给我一些建议吗?

谢谢。

编辑:我的问题可能被指定为,我如何为 "custom" 泛型函数 class 实现 compute-applicable-methods-using-classescompute-applicable-methods,这样如果一些泛型函数方法的特化项是 typeclasses(classes 的 metaclass 是 'typeclass' class),然后对应参数的 class 必须实现类型class(这只是意味着将类型class 存储在参数的 class 的槽中)才能使方法适用?
据我从文档中了解到,当调用泛型函数时,首先调用 compute-discriminating-function ,它将首先尝试通过 compute-applicable-methods-using-classes 获取适用的方法,如果不成功,将尝试与 [=11 相同=].
虽然我对 compute-applicable-methods-using-classes 的定义似乎有效,但通用函数无法分派适用的函数。所以问题一定在compute-discriminating-functioncompute-effective-method

code

我相信您需要覆盖 compute-applicable-methods and/or compute-applicable-methods-using-classes 来计算实现通用函数调用所需的方法列表。然后,您可能需要重写 compute-effective-method,它将该列表和其他一些内容组合成一个函数,可以在 运行 时调用该函数来执行方法调用。

我真的推荐阅读 The Art of the Metaobject Protocol(如前所述),其中对此进行了非常详细的介绍。然而,总而言之,假设您在某些 类 上定义了一个方法 foo(类 不需要以任何方式关联)。计算 lisp 代码 (foo obj) 调用 compute-effective-method 返回的函数,该函数检查参数以确定要调用的方法,然后调用它们。 compute-effective-method 的目的是通过将类型测试编译成 case 语句或其他条件来尽可能多地消除 运行 的时间成本。因此,Lisp 运行time 不必在每次调用方法时都查询所有方法的列表,而只需在添加、删除或更改方法实现时查询。通常所有这些都在加载时完成一次,然后保存到您的 lisp 映像中以获得更好的性能,同时仍然允许您在不停止系统的情况下更改这些内容。

这在 Common Lisp 中不容易实现。

在 Common Lisp 中,操作(泛型函数)与类型(classes)是分开的,即它们不是按类型 "owned"。它们的分派是在运行时完成的,也可以在运行时添加、修改和删除方法。

通常,缺少方法导致的错误仅在运行时发出信号。编译器无法知道是否"well" 使用了泛型函数。

Common Lisp 的惯用方式是使用泛型函数并描述其需求,或者换句话说,Common Lisp 中最接近接口的是一组泛型函数和一个标记 mixin class。但大多数情况下,只指定了一个 protocol,并且它依赖于其他协议。例如,参见 the CLIM specification.

至于类型 classes,这是一个关键特性,它不仅使语言保持完全类型安全,而且在这方面使其具有很强的可扩展性。否则,要么类型系统过于严格,要么缺乏表现力会导致类型不安全的情况,至少从编译器的角度来看是这样。请注意,Haskell 不会或不必在运行时保留对象类型,它会在编译时进行所有类型推断,这与惯用的 Common Lisp 形成鲜明对比。

要在运行时在 Common Lisp 中使用类似于类型 classes 的东西,您有几个选择

如果您选择支持类型 classes 及其规则,我建议您使用 the meta-object protocol:

  • 定义一个新的通用函数 meta-class(即继承自 standard-generic-function

  • compute-applicable-methods-using-classes 专门化为 return false 作为第二个值,因为 Common Lisp 中的 classes 仅由它们的名称表示,它们不是 "parameterizable" 或 "constrainable"

  • 专门compute-applicable-methods检查参数的元classes的类型或规则,相应地调度并可能记住结果

如果您选择仅具有可参数化类型(例如模板、泛型),现有选项是 Lisp Interface Library, where you pass around an object that implements a particular strategy using a protocol. However, I see this mostly as an implementation of the strategy pattern, or an explicit inversion of control,而不是实际可参数化类型。

对于实际的可参数化类型,您可以定义抽象的未参数化 classes,您可以从中使用有趣的名称来实习具体实例,例如lib1:collection<lib2:object>,其中 collectionlib1 包中定义的抽象 class,而 lib2:object 实际上是名称的一部分,具体 class.

最后一种方法的好处是您可以在 CLOS 中的任何地方使用这些 classes 和名称。

主要缺点是您仍然必须生成具体的 classes,因此您可能拥有自己的 defmethod 类宏,该宏将扩展为使用 find-class-like 函数,它知道如何做到这一点。因此破坏了我刚才提到的很大一部分好处,否则你应该遵循在你自己的库中定义每个具体 class 的原则,然后再将它们用作专业化。

另一个缺点是,如果没有进一步的重要管道,这太静态了,不是真正通用的,因为它没有考虑到例如lib1:collection<lib2:subobject> 可以是 lib1:collection<lib2:object> 的子 class,反之亦然。通常,它没有考虑计算机科学中所谓的 covariance and contravariance.

但您可以实现它:lib:collection<in out> 可以用一个逆变参数和一个协变参数表示抽象 class。困难的部分是生成和维护具体 class 之间的关系,如果可能的话。

一般来说,编译时方法在 Lisp 实现级别更合适。这样的 Lisp 很可能不是 Common Lisp。您可以做的一件事是为 Haskell 使用类似 Lisp 的语法。它的完整元循环将使其在宏扩展级别完全类型安全,例如为宏本身生成编译时类型错误,而不仅仅是它们生成的代码。


编辑:在你的问题编辑之后,我必须说 compute-applicable-methods-using-classes 必须 return nil 作为第二个值,只要有在方法中键入 class 专业化程序。您可以 call-next-method 否则。

这与 applicable 方法中的类型 class 专业化程序不同。请记住,CLOS 对 classes 类型一无所知,因此通过 return 对 c-a-m-u-c 中的某些内容进行真正的第二个值,你说它可以记忆(缓存)给定class一个人。

您必须真正专门化 compute-applicable-methods 才能进行正确的类型 class 调度。如果有机会记忆(缓存),你必须在这里自己做。