Is there a naming convention for methods in Common Lisp?

Viewed 238

I found it painful to do object-oriented programming in Common Lisp. The main problem is the naming of methods.Please see the code below.

(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))

The two action methods are not different implementations for a same generic function. They have the same name by accident. But Common Lisp requires all methods with the same name to have the same numbers of parameters.

In order to avoid the name conflicts, I usually prefix the methods' names with the class names, such as foo-action and bar-action. But that causes the code to be quite verbose in real program, e.g. (lengthy-class-name-do-something some-variable).

Other object-oriented programming languages, like C++ and Java, do not have such problems. You can write that like some_variable.do_something() with no name conflicts in them.

So I'd like to know if there exists any better solution to the problem described above?

4 Answers

The key difference is indeed that methods are methods on generic functions. They are not methods on classes.

This may sound like words for words' sake. But it should highly influence your naming choices. "Action" is a pretty bland name for a generic function. When foo acts, what does it actually do? When bar acts, what does it actually do?

If action truly is the best name, would having a foo:action and a bar:action make sense (that is, an action in the foo package, and one in the bar package)? They are different GF's. With, of course, the drawback that it is no longer as easy as just calling action on them.

And that hints towards "you don't need a method, you can just use a function", because you don't need a method to do things with classes in CL.

Those two methods are methods of the same generic function, and so they absolutely must have compatible lambda lists. Otherwise, there is no guarantee that the resolution of the effective method combination (which happens at runtime, as this is runtime polymorphism) has an unambiguous result.

You omitted the defgeneric form, which should give you a warning from the compiler.

This only works in Java as you described because you conventionally put each class into its own namespace. But here, you are working in one namespace and expect the same name (symbol) to have two entirely different meanings. This doesn't work.

In Java parlance, every generic function is its own interface.

Aside, to illustrate the advantage: you don't need to attach implementations of this interface to a class, but you can have even multiple classes to dispatch (at runtime) on. This makes e. g. the notoriously complex Visitor pattern obsolete.

Imagine for a moment such a syntax in Java at toplevel (i. e. as an alternative to class or interface:

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

This method would be invoked when at runtime a call to IAction#action gets objects of class Foo and Bar. Of course, this doesn't exist in Java (and seems prohibitively difficult to add).

There is no general naming convention for methods likewise there's no naming convention for functions. The situation of name conflict may be resolved differently depending on the context.

  1. The first question is: if there are just two methods that have different functionality and clashing names, why not consider ordinary functions (with different names). If you name some function (ordinary or generic) in a very generic way, it implies that this function is ubiquitous (which is quite a rare case). In such a situation, the set of inputs for this function should be more or less uniform (even if it has different methods for different argument types), but you can use &key (and &allow-other-keys) to deal with the variation of additional arguments. However, in most cases, giving more distinct names to two semantically different actions will be more beneficial for future code maintenance.

  2. If you still want to retain the same name for two or more different actions, you can place the relevant code in different packages. That may be quite reasonable to do for code that has different functionality. Now, you won't be able to import both functions at once into some other package because it will still result in a name clash. But you'll be able to use package-prefixed names: p1:foo and p2:foo (if both of them will be exported). Packages are a standard Lisp way of resolving unavoidable name conflicts. In this context, we can say that in many other object-oriented languages classes play this role.

Here is how you're defining your class

(defclass foo () ())

The trouble is that you're not defining your class with any slots. Like this:

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

This adds action which is an accessor (both getter and setter) for class foo,

The :initarg word :action is the word used to give it a value when initializing.

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

We can then access the value

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

I suggest checking out this tutorial on CLOS

Related