Living Without Multimethods

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:

  • Create a new type-specific helper function.
  • Modify my-println to add a new cond invoking the feature-specific helper.

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.237.164