On an established codebase I have a function with a signature like so:
(defn myfn [kw &REST] )
This has survived multiple revisions where another optional arg or two end up added to &REST. We’ve hit the point where it would be better to start using a map of args instead of sequential, to support adding new features. So the new function should be:
(defn myfn [kw optmap] )
However, for reverse-compatibility reasons, I need to maintain support for the original signature as well. I can think of several ways to accomplish this:
Make myfn a multimethod with dispatch on the type of the second arg
Check type within the fn itself
Generate a new method with a different name with the latter signature.
Number 1 seems “correct”, but produces the most code.
Number 2 is the simplest to implement but makes an ugly function.
Number 3 will cause a division in old code and new code (causing vestigial traces of when things were written), but seems in line with the talks against semantic versioning (just make a new thing to reduce ambiguity).
I know this is a common enough problem; what solutions have you chosen, why, and have you had any regrets?
If you’re talking about optional named (keyword) arguments in the legacy code, consider that you will have either one argument (and no options), or 3, 5, 7, … arguments if keyword arguments are provided.
The new arity will have precisely two arguments. They don’t overlap.
You might want the 1-arity left as a convenience for an empty map of arguments.
This is what Rich means when he talks about extensible dynamic systems, I think. No type declarations were killed in the making of this function.
Even if you were to have conflicting arities (old 3-ary conflicting with new system), you could do a check in the beginning and see if you got a keyword (multi-arity) or a map (new system).