【问题标题】:Is there a naming convention for methods in Common Lisp?Common Lisp 中的方法有命名约定吗?
【发布时间】:2023-03-20 01:55:01
【问题描述】:

我发现在 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() 这样写,其中没有名称冲突。

所以我想知道对于上述问题是否有更好的解决方案?

【问题讨论】:

  • "但是 common lisp 要求所有同名方法有相同数量的参数。"这是因为如果您不自己创建,CL 将从defmethod 创建一个通用函数defgeneric。泛型函数定义了参数的数量。

标签: generics common-lisp naming


【解决方案1】:

关键的区别确实是方法是泛型函数上的方法。它们不是类上的方法。

这听起来像是为了文字而说话。但它应该会极大地影响您的命名选择。 “Action”对于一个通用函数来说是一个相当平淡的名字。当foo 行动时,它实际上做了什么?当bar 行动时,它实际上做了什么?

如果action 真的是最好的名字,那么有一个foo:action 和一个bar:action 是有意义的(也就是说,foo 包中的一个actionbar 包中的一个) ?他们是不同的GF。当然,缺点是不再像在他们身上调用action 那样简单。

这暗示了“你不需要方法,你可以只使用一个函数”,因为你不需要一个方法来处理 CL 中的类。

【讨论】:

  • 这完全正确。如果两个函数做不同的事情并接受不同的参数,它们应该有不同的名字——通常是在包系统的帮助下。
【解决方案2】:

这两个方法同一个泛型函数的方法,因此它们绝对必须具有兼容的 lambda 列表。否则,无法保证有效方法组合的解析(发生在运行时,因为这是运行时多态性)具有明确的结果。

您省略了defgeneric 表单,它应该会从编译器向您发出警告。

这仅适用于您所描述的 Java,因为您通常将每个类放入其自己的命名空间中。但是在这里,您在一个命名空间中工作,并期望相同的名称(符号)具有两种完全不同的含义。这不起作用。

用 Java 的说法,每个泛型函数都是它自己的接口。

另外,为了说明优势:您不需要将此接口的实现附加到一个类,但您甚至可以有多个类来调度(在运行时)。这使得 e。 G。众所周知的复杂访客模式已过时。

想象一下 Java 中的顶级语法(即作为classinterface 的替代方案:

method IAction (Foo foo, Bar bar) {
  action (Foo foo, Bar bar) {
    return something(...);
  }
}

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

【讨论】:

    【解决方案3】:

    方法没有通用的命名约定,函数也没有命名约定。名称冲突的情况可能会因上下文而有所不同。

    1. 第一个问题是:如果只有两个方法具有不同的功能和冲突的名称,为什么不考虑普通函数(具有不同的名称)。如果您以非常通用的方式命名某个函数(普通或通用),则意味着该函数无处不在(这种情况非常罕见)。在这种情况下,这个函数的输入集应该或多或少是统一的(即使它对不同的参数类型有不同的方法),但你可以使用&key(和&allow-other-keys)来处理额外的论点。但是,在大多数情况下,为两个语义上不同的操作赋予更多不同的名称将更有利于未来的代码维护。

    2. 如果您仍然想为两个或多个不同的操作保留相同的名称,您可以将相关代码放在不同的包中。对于具有不同功能的代码,这可能是相当合理的。现在,您将无法同时将这两个函数导入到其他包中,因为它仍然会导致名称冲突。但是您将能够使用以包为前缀的名称:p1:foop2:foo(如果它们都将被导出)。包是解决不可避免的名称冲突的标准 Lisp 方法。在这种情况下,我们可以说在许多其他面向对象语言中,类都扮演着这个角色。

    【讨论】:

      【解决方案4】:

      这是定义类的方式

      (defclass foo () ())
      

      问题是你没有用任何槽定义你的类。像这样:

      (defclass foo ()
          ((action 
              :initarg :action
              :accessor action)))
      

      这添加了action,它是类 foo 的访问器(getter 和 setter),

      :initarg:action 是初始化时用来给它一个值的字。

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

      然后我们可以访问该值

      (action my-foo-class)
      output> "bar"
      

      我建议查看tutorial on CLOS

      【讨论】:

      • 您在响应中混淆了方法(操作)和槽(数据)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-15
      • 1970-01-01
      • 1970-01-01
      • 2012-04-18
      • 2014-10-24
      相关资源
      最近更新 更多