The best way to appreciate multimethods is to spend a few minutes living without them, so let’s do that. Clojure can already print anything with print/println. But pretend for a moment that these functions don’t exist and that you need to build a generic print mechanism. To get started, create a my-print function that can print a string to the standard output stream *out*:
| (defn my-print [ob] |
| (.write *out* ob)) |
Next, create a my-println that calls my-print and then adds a line feed:
| (defn my-println [ob] |
| (my-print ob) |
| (.write *out* " ")) |
The line feed makes my-println’s output easier to read when testing at the REPL. For the rest of this section, you’ll make changes to my-print and test them by calling my-println. Test that my-println works with strings:
| (my-println "hello") |
| | hello |
| -> nil |
That’s nice, but my-println doesn’t work so well with nonstrings such as nil:
| (my-println nil) |
| -> java.lang.NullPointerException |
It’s not a big deal though. Just use cond to add special-case handling for nil:
| (defn my-print [ob] |
| (cond |
| (nil? ob) (.write *out* "nil") |
| (string? ob) (.write *out* ob))) |
With the conditional in place, you can print nil with no trouble:
| (my-println nil) |
| | nil |
| -> nil |
Of course, there are still all kinds of types that my-println can’t deal with. If you try to print a vector, neither of the cond clauses will match, and the program will print nothing at all:
| (my-println [1 2 3]) |
| -> nil |
By now you know the drill. Just add another cond clause for the vector case. The implementation here is a little more complex, so you might want to separate the actual printing into a helper function, such as my-print-vector:
| (require '[clojure.string :as str]) |
| (defn my-print-vector [ob] |
| (.write *out*"[") |
| (.write *out* (str/join " " ob)) |
| (.write *out* "]")) |
| |
| (defn my-print [ob] |
| (cond |
| (vector? ob) (my-print-vector ob) |
| (nil? ob) (.write *out* "nil") |
| (string? ob) (.write *out* ob))) |
Make sure that you can now print a vector:
| (my-println [1 2 3]) |
| | [1 2 3] |
| -> nil |
my-println now supports three types: strings, vectors, and nil. And you have a road map for new types: just add new clauses to the cond in my-println. But it’s a crummy road map, because it conflates two things: the decision process for selecting an implementation and the specific implementation detail.
You can improve the situation somewhat by pulling out helper functions like my-print-vector. But then you’ll have to make two separate changes every time you want to a add new feature to my-println:
What you want is a way to add new features to the system by adding new code in a single place, without having to modify any existing code. Clojure offers this by way of protocols, covered in Protocols, and multimethods.
3.15.237.164