Wednesday, March 19, 2025

Object Last or First

In Java or C#, the first argument to a method call is the object that contains the method. This is built in to the language and it is to some extent an artifact of the mechanism of method dispatch. So when you add a type (class) to the language, you will have a set of methods where the first argument is the object of that type.

In Common Lisp, the first argument to a function is not special. When you add a type, the functions that operate on that type can place the object anywhere in the argument list. The convention in Common Lisp is (mostly) for the object to be the last argument in the argument list. Look at the list and sequence functions for examples of this convention.

The Common Lisp convention reads more like English. (subst new old sequence) reads directly as "substitute new for old in sequence". In Java, the same method would be called like this sequence.subst(old, new), which would have to read as "In sequence, substitute for old, new", which is a bit more awkward.

But I think I prefer the Java convention. In Lisp, this would be (substitute sequence old new). There is no implementation need for the object to be the first argument, but I think there is an advantage to the regularity of the convention. It places the object in the same place in the argument list for all the functions that operate on the object.

The argument that has persuaded me the most is that if the return type of the function is the same type as the object, as it often is, then you can elegantly chain the method calls together with a fold-left. So consider a table object. It might have an insert method that takes a key and value and returns a new table. If the insert method is like this: (insert table key value), then you can insert a bunch of keys and values with a fold-left like this: (fold-left #’insert table ’(key1 key2 ...) ’(value1 value2 ...)).

Note how order of arguments is analagous between the fold-left and the insert method. When the object is the last argument, then you have to insert an intermediate lambda expression to shuffle the arguments around, and the table argument moves from being after the key and value in the insert method to being before the key list and value list in the fold-left method. It is a small thing, but I find it very appealing that in moving from the single argument case to the argument list case we don’t have random changes.

Of course I don’t think we should change Common Lisp to conform to a different convention, but I tend to write my own functions with the object as the first argument rather than the last.

3 comments:

Anonymous said...

My first OO language was Objective-C, which is heavily influenced by Smalltalk and message passing, so object first feels more natural to me. I like the idea that I'm addressing the object: "Hey list, append this to yourself!"

Anonymous said...

Agreed. CL unfortunately has quite a few collection operations that take the collection second: 'member', 'assoc', 'push', and 'gethash' all come to mind. And of course 'cons'. I'm sure there are more. Most of them probably go back to Lisp 1.5, so changing them was out of scope for CL. The trend started to reverse, I guess, with the introduction of 'aref', which needs the array to come first in order to handle multidimensional arrays, since they take varying numbers of subscripts.

In FSet, I have been careful in most cases to write operations that take the collection first; e.g., '(contains? a-set x)', in contrast to CL's `(member x a-list)'. The exceptions are, or should be, all higher-order functions that take a function as first argument, like 'reduce'; in those cases, the collection is second. ('fset:reduce' upward-compatibly shadows 'cl:reduce'; I obviously didn't want to change the argument order.)

Anonymous said...

Object first allows chaining as seen Elixir (and other languages):

list
|> Enum.reverse()
|> Enum.map(fn x -> x * x end)

The |> operator basically puts the result of the previous expression as first argument.