Recently in code review Boaz questioned my choice of the order of arguments in a function definition. I couldn’t come up with a coherent reason for my choice, and he couldn’t put words to his feeling that it was odd. As often happens, I realised what was going on while doing something entirely non-coding-related; this time I was in the shower when the light dawned. It turns out that the argument order tells you something about the language paradigm I’m thinking in (OO versus functional); it also ties in to the choice between destructive updates or returning a modified copy, which came up in the same code review.

The function definition (in Python) looks something like this:

def remove_brands_from_chart_defn(chart_defn, brands_to_remove):

A chart definition is a complex structure of nested dictionaries and lists (it comes from JSON serialisation of a JavaScript object in the browser); a brand is an element that can occur in many places in a chart definition, so the function has to make a recursive traversal of the chart definition’s structure to find all occurrences of the brands it has to remove.

Now there are two ways of thinking about this process, which correspond to the two possible orderings of the function arguments. One is that a chart definition is an object, which has the behaviour “remove these brands from my internal structure”. This would correspond more directly to the Python definition:

class ChartDefinition(object):
  def remove_brands(self, brands_to_remove):

As it happens, the chart definition is simply a Python dictionary read from a JSON string, so we don’t have a class to define this method on. (Some considerations I’ll get to later suggest that we ought to have one, but I’ll leave that aside for the moment.) You can see the object-oriented argument pattern though: the first argument gives the object being operated on (which languages like Java make entirely implicit in a method’s argument structure) while the rest of the arguments specify the particular form the operation will take (which brands get removed, in this case).

The other argument ordering is much more reminiscent of functional programming. Here “removing brands X and Y” is a specific form of a more general operation “removing brands”, and the specialised form gets applied to a particular data structure. Haskell makes this very explicit with its syntactic support for currying:

remove_brands :: [Brand] -> ChartDefn -> ChartDefn
remove_brands_X_and_Y = remove_brands [brand_x, brand_y]

Here remove_brands_X_and_Y is now a one-argument function that wants a chart definition, and will perform the brand-removal operation on it. (Of course you could also think of the operation as “remove brands from chart definition X”, and depending on your use case that might be the more useful currying to apply, but that perspective is (perhaps) less functional and (definitely) more object-oriented.)

So my choice of argument order reveals some aspects of my coding psychology: in this case, at least, it seems I was “thinking OO” even when I wasn’t using Python’s class system at all. And indeed, the issue of destructive updates versus return-modified-copy bears this out.

Remember that a chart definition is a complex structure of nested dictionaries and lists, and that the brands to be removed can occur at many different positions in this structure. Of course I handled the removal with a recursive traversal of the structure, but given this there are still two ways to get the resulting chart definition back: I could modify the structure in-place (“destructive update”) or I could return a copy of the structure with the modifications occurring only in the copy (“return-modified-copy”).1

How to choose between the two? Of course, the particular situation might make one or other choice obvious. (For instance, it’s elsewhere very handy for us to be able to extract by reference a list of brands occurring in a chart definition, so that altering those brands destructively updates the chart as well.) But the language paradigm can also help with making this decision.

The destructive update is very natural in the OO setting: one of the behaviours of objects of this type is to update their internal state by removing internal references to a given list of brands. Thinking functionally, on the other hand, the destructive side effect is unexpected and unnecessary, while return-modified-copy is much more natural.

The odd thing about this situation is that because our chart definition is not an object (at least not of a user-defined type), I had written an OO-like function to perform a destructive update. In the course of writing this post I’ve convinced myself that this is a recipe for misunderstanding: either that function should be functional-style (return-modified-copy and –yes Boaz– with the arguments reversed), or it should be a (destructive) method defined on a ChartDefinition class and returning no result at all.

Notes:

  1. To my shame, what I actually did was a bit of both, which would have introduced extremely difficult to diagnose bugs further down the pipeline if Boaz hadn’t asked me to put my choice in a docstring, which made me verbalise it explicitly, in turn making me notice that I hadn’t actually made the choice at all. []