One good use for default function arguments
When I wrote about a danger of default function arguments I mentioned that there are cases where they make sense and are useful. Today I'm going to present what I feel is one of them.
To put it simply, one use for default arguments is when you effectively have a bunch of slightly different APIs but it would be awkward to have different functions for them. Not uncommonly you might have too many function variants, the code would be too entwined, or both. Reasonably chosen default arguments effectively give you multiple APIs; one with all the defaults, another with one set of defaults overridden, and so on. You can in fact discover how many different APIs you actually need more or less on the fly, as you write code that uses different combinations of default and non-default arguments.
All of that sounds really abstract, so I'll use an actual example from DWiki. DWiki has a concept of 'views', which are both different ways to present the same underlying bit of the filesystem and different ways of processing URLs. Views have names and handler functions, and there is a registration function for them:
def register(name, factory, canGET = True, canPOST = False, onDir = False, onFile = True, pubDir = False, getParams = , postParams = ): [....]
This is effectively several APIs in one. Fully expanded, I think it'd
be one API that's used to register forms (that's what
False value for
postParams are for), one
API for views of directories only, one API for views of files only,
and one API for views that work on both files and directories. As
separate functions, each would have a subset of the full arguments for
register(). But equally, as separate functions they would all do the
same thing and they'd have basically the same name (there is no natural
strong name difference between 'register a form view' and 'register a
directory view' and so on).
I dislike small variations of things (I'm driven to generalize), so when I was writing DWiki I didn't make separate functions for each API; instead I slammed them together into one function with a bunch of default arguments. The simplest case (with all arguments as defaults) corresponds to what I thought at the time was the most common case, or at least the base case.
(This view of default arguments creating multiple APIs comes from a bit in this talk on Go; reading it crystallized several things in my head.)