【问题标题】:Changing method dispatch in Common Lisp在 Common Lisp 中更改方法调度
【发布时间】:2015-11-19 05:26:42
【问题描述】:

我正在尝试使用 Common Lisp 的 CLOS 模拟类似于 Haskell 的类型类的东西。也就是说,我希望能够在对象的“类型类”而不是其超类上调度方法。

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

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

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

谢谢。

编辑:我的问题可能被指定为,我如何为“自定义”泛型函数类实现compute-applicable-methods-using-classescompute-applicable-methods,这样,如果泛型函数方法的某些专家是类型类(元类的类是'typeclass'类),那么相应参数的类必须实现类型类(这只是意味着将类型类存储在参数类的插槽中)才能应用该方法?
据我从文档中了解到,当调用泛型函数时,首先调用compute-discriminating-function,它将首先尝试通过compute-applicable-methods-using-classes获取适用的方法,如果不成功,将尝试与compute-applicable-methods相同。
虽然我对compute-applicable-methods-using-classes 的定义似乎有效,但通用函数无法调度适用的函数。所以问题一定出在compute-discriminating-functioncompute-effective-method

code

【问题讨论】:

标签: common-lisp dispatch clos mop


【解决方案1】:

这在 Common Lisp 中不容易实现。

在 Common Lisp 中,操作(泛型函数)与类型(类)是分开的,即它们不为类型“拥有”。它们的调度是在运行时完成的,也可以在运行时添加、修改和删除方法。

通常,缺少方法的错误只会在运行时发出信号。编译器无法知道泛型函数是否被“很好地”使用。

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

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

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

如果你选择用它的规则来支持类型类,我建议你使用the meta-object protocol:

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

  • 特殊化 compute-applicable-methods-using-classes 以返回 false 作为第二个值,因为 Common Lisp 中的类仅由它们的名称表示,它们不是“可参数化的”或“可约束的”

  • 专门化 compute-applicable-methods 来检查参数的元类的类型或规则,进行相应的调度并可能记忆结果

如果您选择仅具有可参数化的类型(例如模板、泛型),现有选项是 Lisp Interface Library,您可以在其中传递使用协议实现特定策略的对象。但是,我认为这主要是 the strategy pattern 的实现,或显式 inversion of control,而不是实际的可参数化类型。

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

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

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

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

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

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


编辑:在您的问题编辑之后,我必须说 compute-applicable-methods-using-classes 必须在方法中有类型类专用器时将 nil 作为第二个值返回。否则你可以call-next-method

这与 适用 方法中有一个类型类专门化器不同。请记住,CLOS 对类型类一无所知,因此通过从 c-a-m-u-c 返回带有真正第二个值的内容,您是在说仅给定类就可以记忆(缓存)。

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

【讨论】:

  • 感谢这些建议。我主要要实现的是类型类的默认方法定义的继承,运行时类型类成员资格和满意度检查(类型类的所有必需方法都由类实现),以及类型类继承和关系(例如,类型类自动实现默认方法)对于另一个类型类)。所以我知道我必须实现自己的compute-applicable-methods,这将是这项工作的主要困难。
  • 好的,我明白了。虽然这并不能解释为什么它没有使用来自 c-a-m-u-c 的返回值。谢谢。
【解决方案2】:

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

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

【讨论】:

  • 好的,谢谢。我打算给自己买一份 AMOP。
  • (便携式)优化方法是在compute-applicable-methods 级别进行记忆。方法组合具有完全不同的含义,例如你仍然可以拥有(defmethod first-result :or ((a equatable<person>)) ...),因此first-result 仍然可以是具有专用元类和compute-applicable-methods 的通用函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多