Common Lisp中有方法的命名惯例吗?

3

我发现在Common Lisp中进行面向对象编程很痛苦。主要问题在于方法的命名,请参见下面的代码。

(defclass foo () ())

(defmethod action ((object foo))
  (write-line "hello"))


(defclass bar () ())

(defmethod action ((object bar) boo)
  t)


(print
 (action (make-instance 'foo)))

(print
 (action (make-instance 'bar) 1))


这两个action方法并不是同一个通用函数的不同实现,只是因为巧合而具有相同的名称。但是,在Common Lisp中,要求具有相同名称的所有方法必须具有相同数量的参数。
为了避免命名冲突,我通常使用类名前缀来命名方法,例如foo-actionbar-action。但是在真正的程序中,这会使代码变得非常冗长,例如(lengthy-class-name-do-something some-variable)
其他面向对象编程语言(如C++和Java)没有这样的问题。您可以像这样编写some_variable.do_something(),其中它们之间不存在名称冲突。
因此,我想知道是否存在更好的解决上述问题的方法?

1
但是Common Lisp要求所有具有相同名称的方法具有相同数量的参数。这是因为如果您不自己创建它,CL将从defmethod创建一个通用函数defgeneric。通用函数定义了参数的数量。 - Manfred
4个回答

7
关键区别确实在于方法是通用函数上的方法,而不是类上的方法。
这听起来可能只是纯粹的措辞。但它应该极大地影响您的命名选择。 "Action" 对于通用函数来说是一个相当平淡无奇的名称。当 foo 执行操作时,它实际上在做什么?当 bar执行操作时,它实际上在做什么?
如果 action 真的是最好的名称,那么使用 foo:actionbar:action 是否有意义(也就是说,在 foo 包和 bar 包中都有一个 action )?它们是不同的通用函数。当然,缺点是不再像以前那样轻松地调用它们的 action
这提示着“你不需要一个方法,你可以使用一个函数”,因为在 CL 中,您不一定需要一个方法来处理类的事情。

3
完全正确。如果两个函数执行不同的操作并接受不同的参数,它们应该有不同的名称——通常需要借助包系统来实现。 - Xach

4
这两种方法是同一个通用函数的方法,因此它们必须有兼容的lambda列表。否则,在运行时多态的情况下,有效方法组合的解析就没有明确的结果。
你忽略了defgeneric形式,这会让编译器发出警告。
这只在Java中按照你描述的方式起作用,因为通常将每个类放到自己的命名空间中。但在这里,你正在一个命名空间中工作,并期望相同的名称(符号)具有两个完全不同的含义。这是行不通的。
按照Java的说法,每个通用函数都是它自己的接口。
另外,为了说明其优点:你不需要将此接口的实现附加到类上,而是可以在运行时对多个类进行调度。这使得像复杂的访问者模式这样的模式变得过时了。
想象一下在Java中顶层的这样一种语法(即作为class或interface的替代选择):
method IAction (Foo foo, Bar bar) {
  action (Foo foo, Bar bar) {
    return something(...);
  }
}

当运行时调用IAction#action并获取到类FooBar的对象时,将会调用该方法。当然,在Java中不存在这种情况(而且似乎很难添加)。


1
没有一般的方法命名规则,同样也没有函数的命名规则。名称冲突的情况可能会因上下文而有不同的解决方法。
第一个问题是:如果只有两个具有不同功能和冲突名称的方法,为什么不考虑普通函数(使用不同的名称)呢?如果您以非常通用的方式命名某个函数(普通或通用),则意味着该函数是普遍存在的(这是相当罕见的情况)。在这种情况下,该函数的输入集应该是更加统一的(即使对于不同的参数类型它也有不同的方法),但是您可以使用“&key”(和“&allow-other-keys”)来处理附加参数的变化。然而,在大多数情况下,为两个语义不同的操作提供更明显的名称将更有利于未来的代码维护。
如果您仍然想保留两个或更多不同操作的相同名称,则可以将相关代码放置在不同的包中。对于具有不同功能的代码,这是非常合理的做法。现在,您将无法同时将两个函数导入到其他包中,因为这仍然会导致名称冲突。但是,您可以使用包前缀名称:p1:foo和p2:foo(如果它们都被导出)。包是解决不可避免的名称冲突的标准Lisp方法。在这种情况下,我们可以说,在许多其他面向对象的语言中,类扮演着这个角色。

-2

这是你定义类的方式

(defclass foo () ())

问题在于你没有为你的类定义任何插槽。像这样:
(defclass foo ()
    ((action 
        :initarg :action
        :accessor action)))

这个代码添加了一个名为action的访问器(既是getter也是setter),用于类foo。

:initarg单词:action是在初始化时赋值的关键字。

(defvar my-foo-class (make-instance 'foo :action "bar" ))

我们可以访问该值。
(action my-foo-class)
output> "bar"

我建议查看这个CLOS教程


你在回答中混淆了方法(操作)和插槽(数据)。 - Vsevolod Dyomkin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接