Endless Parentheses

Ramblings on productivity and technical subjects.

profile for Malabarba on Stack Exchange

New Clojure lib: lazy-map

The concept of a lazy-map might sounds odd at first. How do you know if a map contains an entry without resolving the whole map? But it’s not the entries that are lazy, it’s the values they hold. See this example from the Readme:

user> (def my-map
        (lazy-map {:cause (do (println "Getting Cause")
                              :major-failure)
                   :name (do (println "Getting Name")
                             "Some Name")}))
#'user/my-map

user> (:name my-map)
Getting Name
"Some Name"

user> (:name my-map)
"Some Name"

user> (:cause my-map)
Getting Cause
:major-failure

Lazy-map is on Clojars, so you can just add it as a dep and play around:

How would this ever be useful?

One of my side projects a few months ago (when I was playing around with Clojure-on-Android) was an Android client for a desktop Java application. Because of this, a lot of the code was about interfacing with Java objects defined by this package. These objects were from many different classes, but the one thing they had in common is that they were full of get methods.

I wanted so much to be able to use these objects as maps that I wrote a protocol for converting general objects to Clojure maps. Here’s an example of how it worked on a string.

user> (to-map "My own String!")
{:to-char-array #object["[C" 0xdf4ddb3 "[C@df4ddb3"],
 :empty? false,
 :to-string "My own String!",
 :chars #object[java.util.stream.IntPipeline$Head 0xad35343 "java.util.stream.IntPipeline$Head@ad35343"],
 :class java.lang.String,
 :length 14,
 :trim "My own String!",
 :bytes #object["[B" 0x75ef7d8f "[B@75ef7d8f"],
 :hash-code 1673659170,
 :object "My own String!",
 :to-upper-case "MY OWN STRING!"}

For comparison, here’s how bean works (a similar function from clojure.core).

user> (bean "My own String!")
{:bytes #object["[B" 0x1ad60072 "[B@1ad60072"],
 :class java.lang.String,
 :empty false}

The protocol is actually quite smart. It uses a number of heuristics to only convert methods that look like they’re side-effect free. Of course, it’s not foolproof (this is Java we’re talking about), but the macro used to extend the protocol lets you specify methods to exclude.

The only problem was the performance cost. Some of these methods were very expensive to run, and eagerly calling all methods of all objects just so I could later access some of these was obviously a bad deal. The solution, clearly, was to only call these methods when the map entries were actually accessed. And so lazy-map was born.

Tags: clojure, emacs,

comments powered by Disqus