Name this operation by Mark Seemann
A type of operation seems to be repeatedly appearing in my code, but I can't figure out what to call it; can you help me?
Across various code bases that I work with, it seems like operations (methods or functions) like these keep appearing:
public IDictionary<string, string> Foo( IDictionary<string, string> bar, Baz baz)
In order to keep the example general, I've renamed the method to Foo
, and the arguments to bar
and baz
; in this article throughout, I'll make heavy use of metasyntactic variables. The types involved aren't particularly important; in this example they are IDictionary<string, string> and Baz, but they could be anything. The crux of the matter is that the output type is the same as one of the inputs (in this case, IDictionary<string, string> is both input and output).
Another C# example could be:
public int Qux(int corge, string grault)
Notice that the types involved don't have to be interfaces or complex types. The only recurring motif seems to be that the type of one of the arguments is identical to the return type.
Although the return value shares its type with one of the input arguments, the implementation doesn't have to echo the value as output. In fact, this is often not the case.
At this point, there's also no implicit assumption that the operation is referentially transparent. Thus, we may have implementation where some database is queried. Another possibility is that the operation is a closure over additional values. Here's an F# example:
let sign calculateSignature issuer (expiry : DateTimeOffset) claims = claims |> List.append [ { Type = "signature"; Value = calculateSignature claims } { Type = "issuer"; Value = issuer } { Type = "expiry"; Value = expiry.ToString "o" }]
You could imagine that the purpose of this function is to sign a set of security claims, by calculating a signature and add this and other issuer claims to the input list of claims. The signature of this particular function is (Claim list -> string) -> string -> DateTimeOffset -> Claim list -> Claim list
. However, the leftmost arguments aren't going to vary much, so it would make sense to partially apply the function. Imagine that you have a correctly implemented signature function called calculateHMacSha, and that the name of the issuer should be "Ploeh":
let sign' = sign calculateHMacSha "Ploeh"
The type of the sign' function is DateTimeOffset -> Claim list -> Claim list
, which is equivalent to the above Foo and Qux methods.
The question is: What should we call such an operation?
Why care?
In its general form, such an operation would have the shape
public interface IGrault<T1, T2> { T1 Garply(T1 waldo, T2 fred); }
in C# syntax, or 'a -> 'b -> 'b
type in F# syntax.
Why should you care about operations like these?
It turns out that they are rather composable. For instance, it's trivial to implement the Null Object pattern, because you can always simply return the matching argument. Often, it also makes sense to make Composites out of them, or chain them in various ways.
In Functional Programming, you may just do this by composing functions together (while not particularly considering the terminology of what you're doing), but in Object-Oriented languages like C#, we have to name the abstraction before we can use it (because we have to define the type).
If T2
or 'a
is a predicate, the operation might be a filter, but as illustrated with the above sign' function, the operation may also enrich the input. In some cases, it may even replace the original input and return a completely different value (of the same type). Thus, such an operation could be a 'filter', an 'expansion', or a 'replacement'; those are quite vague properties, so any name for such operations are likely to be vague or broad as well.
My question to you is: what do we call this type of operation?
While I do have a short-list of candidate names, I encounter these constructs so often that I'd be surprised if such operations don't already have a conceptual name. If so, what is it? If not, what should we call them?
(I'd hoped that this type of operation was already a 'thing' in functional programming, so I asked on Twitter, but the context is often lost on Twitter, so therefore this blog post.)
Comments
Hey Mark,
In a mathematical sense, I would say that this operation is an accumulation: you take a (complex or simple) value and according to the second parameter (and possible other contextual information), this value is transformed, which could mean that either subvalues are changed on the (mutable) target or a new value (which is probably immutable) is created from the given information. This whole process could imply that information is added or stripped from the first parameter.
What I found interesting is that there is always exactly one other parameter (not two or more), and it feels like the information from two is incorporated into parameter one.
Thus I would call this operation a Junction or Consolidation because information of parameter 2 is joined with parameter 1.
Kenny, thank you for your suggestions. Others have pointed out the similarities with accumulation, so that looks like the most popular candidate. Junction and Consolidation are new to me, but I've added both to my list.
When it comes to the second argument, I decided to present the problem with only a second argument in order to keep it simple. You could also have variations with two or three arguments, but since you can rephrase any parameter list to a Parameter Object, it's not particularly important.
Dear Mark,
I choose Junction over accumulation because it is a word that (almost) everybody is familiar with: a junction of rivers. When two or more rivers meet, they form a new river or the tributary rivers flow into the main river. I think this natural phenomenon is a reasonable metaphor for the operation your describing in your post.
Accumulation in contrast is specific to mathematics and IMHO I don't think everybody would understand the concept immediately (especially when lacking academic math education). Thus I think the name Junction can be grasped more easily - especially if you think of other OOP patterns like Factory, Command, Memento or Decorator: the pattern name provides a metaphor to a thing or phenomena (almost) everybody knows, which make them easy to learn and remember.
It was never my intention to imply that I necessarily considered Accumulation the best option, but it has been mentioned independently multiple times.
At first, I didn't make the connection to river junctions, but now that you've explained the metaphor, I really like it. It's like a tributary joining a major river. The Danube is still the Danube after the Inn has joined it. Thank you.
Sorry, I didn't want to sound like a teacher :-)
Not sure if you came to a conclusion here. Here are some relevant terms I've witnessed other places.
I'm curious, did you experiment with this pattern in a language without partial application? It seems like shepherding operations into the shared type would be verbose.
Spencer, thank you for writing. You're certainly reaching back into the past!
Identifying a name for this kind of API was something that I struggled with at least as far back as 2010, where, for lack of better terms at the time I labelled them Closure of Operations and Reduction of Input. As I learned much later, these are essentially endomorphisms.
I've learned a lot from your content! I decided to start at the beginning to experience your evolution of thought over time. I love how deeply you think and your clear explanations of complex ideas. Thank you for sharing your experience!
Endomorphism makes sense. I got too focused on my notion of piping and forgot the key highlighted value was closure.