Implicit vs. Explicit

One common piece of programming advice out you'll get is to prefer for things to be "explicit" rather than "implicit". There are a lot of meanings to this saying, and I don't think it's usually described in much detail, so I'll give you an example:

Most programming languages have functions. In our program, we could call functions themselves "implicit". The explicit version of this (especially in object-oriented languages) might be the "command" pattern.

Why is a function "implicit"?

Functions are built-in to the programming languages we use, so an example in Java might be:

1
2
3
4
5
Integer double(Integer input) {
  return input * 2;
}

Integer doubled = double(5); // returns 10

Functions are executed by the compiler/interpreter directly, at the location the code was written. If they have side effects, they happen immediately. Based on where the code is in the program, and from where it is invoked - that is when it takes effect.

It's hard to inspect a function, or augment it. Some languages have reflection, but they are hard to work with - you only get some meta-information about the code, and can't augment the inspection mechanism as it's built into the language.

It's hard to audit what happened - there's no record of what functions executed. Once again, since the compiler/interpreter is executing the code for you, you don't get a record of what executed - you have to build some other way of representing this - like logs. But logs generally aren't very inspectable either - usually for human consumption and hard to parse structurally. Not only that, but it's likey hard or at least awkward to access this information from the program itself at runtime.

You usually can't serialize functions. Since functions contain arbitrary code, and generally can have any number of hidden effects or dependencies (unless we're writing Haskell), it's not simple or even possible to serialize functions themselves.

These aren't things we normally think about as "problems". Of course these things are true - we write code and functions to execute something. We aren't generally looking to manipulate the code itself... it can easily get too abstract. However, if you are in the position that you want to do any of these things, you are going to have a hard time using functions directly.

How is the "Command" pattern explicit?

For those not familiar, the "command" pattern is the idea of representing an abstract "command" as an object or datastructure in the code. Something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Command<I,O> {
  String name();
  O execute(I input);
}

class DoublingCommand implements Command<Integer,Integer> {
  String name() {
    return "double";
  }
    
  Integer execute(Integer input) {
    return input * 2;
  }
}

Command<Integer> doubler = new DoublingCommand();
Integer doubled = doubler.execute(5); // returns 10

We definitely have more code here now, but what do we get from it?

We can inspect the data. If you have a Command object, you can call command.name() and get the name of it. You could even add more methods to it for other types of data - like a description, or even a reference to a list of commands that it depends on or invokes.

We can "evaluate" it 0 to N times. Since it's just a piece of data, we can store it in a variable and decide when or if we want to execute it. Of course, in more functional languages, this is possible with raw functions - but not in Java.

We can store it in a database. Commands now have a concrete representation, and could be stored in a database. It's true that the code within the object itself wouldn't be stored - but as long as we aren't changing the semantics of the effect that is represented by the object, we are fine. We can now look at a list of commands store into the database and understand what happened - we can audit the actions in a meaningful way.

We can "undo" it by augmenting the object. We could define an "undo" method that takes the output and reverses the effect. Of course, this isn't possible with all types of transformations, but it very well might work perfectly for your problem domain. The point is that we can define arbitrary additional functionality and attach it to the command itself.

Easy to test if it's the return value of a function. If you represent the object as some sort of external "effect", returning it from a function instead of invoking that effect directly can give us a huge advantage. By returning the command, we can write a test that simply looks at the returned command and validates whether it makes sense for the given input. If we were to execute the side effect directly from within the tested function, we'd have to look for the visible side-effects elsewhere in the system, or maintain costly mocks.

We can send it over the wire - i.e. it's language agnostic. Since we are defining the operations we can do in terms of data, we can serialize it over the wire and have a completely different programming language carry out the instruction.

It's no wonder that there are many programs that utilize this pattern - probably the most popular being UIs that desire "undo" functionality. You can represent the actions possible in your UI as "commands" that have an effect - and then you simply keep track of a list of commands that have run. To undo, you either invoke a special undo() function on the command, or alternately you could replay all the previous commands from a known-fresh state.

Code as Data

Now, I will say that there are languages that are considered homoiconic such as Clojure. What this means is "code is data" such that actual code can be "escaped" in a way that prevents it from being executed immediately, and instead gets stored as a structure that can be iterated over and manipulated (usually nested lists if the language is lisp-like). This accomplishes some of the points made above, but doesn't exactly support augmenting it with arbitrary additional functionality and metadata. It's much more flexible than other languages, but still doesn't give you the ultimate freedom that you get by making these concepts "first class citizens" in the code. Not only that, but this idea is much more powerful than the Command pattern itself - and that's a topic for another post.

Conclusion

Of course, to interpret these command objects that have meta-information, we need to write actual language-specific code to execute them. We can't, and wouldn't want to apply this pattern everywhere - as it adds extra code, and a level of abstraction/indirection into the system. However, this is a powerful pattern to apply to many problem domains. Although it doesn't always work this way - the concept is similar in nature to DSLs in that we have a language embedded in a language. By making this language less powerful than the host language, we restrict the types of incorrect programs we can generate, and overall make the system easier to maintain and reason about.