Search code examples
timeclojuremacros

Why does time need to be a macro?


The Clojure source code gives the following definition of time

(defmacro time
  "Evaluates expr and prints the time it took.  Returns the value of
 expr."
  {:added "1.0"}
  [expr]
  `(let [start# (. System (nanoTime))
         ret# ~expr]
     (prn (str "Elapsed time: " (/ (double (- (. System (nanoTime)) start#)) 1000000.0) " msecs"))
     ret#))

Why does this need to be a macro? What about it could not be done with a function?


Solution

  • Because function evaluates its arguments before it starts working with them. You can write time as function and try to call it:

    (defn my-time [expr]
      (let [start (System/nanoTime)
            ret expr]
        (prn (str "Elapsed time: " (/ (double (- (System/nanoTime) start)) 1000000.0) " msecs"))
        ret))
    
    (my-time (Thread/sleep 1000))
    "Elapsed time: 0.005199 msecs"
    => nil
    

    Compare that with time macro:

    (time (Thread/sleep 1000))
    "Elapsed time: 1000.1222 msecs"
    => nil
    

    Function my-time evaluated argument (Thread/sleep 1000), symbol expr got value nil and then body of that function happened- two (System/nanoTime) were called shortly after each other, because value of expr was already computed.

    Macro time didn't evaluate (Thread/sleep 1000), but expanded to:

    (macroexpand `(time (Thread/sleep 1000)))
    =>
    (let*
     [start__6136__auto__ (. java.lang.System (clojure.core/nanoTime)) ret__6137__auto__ (java.lang.Thread/sleep 1000)]
     (clojure.core/prn
      (clojure.core/str
       "Elapsed time: "
       (clojure.core//
        (clojure.core/double (clojure.core/- (. java.lang.System (clojure.core/nanoTime)) start__6136__auto__))
        1000000.0)
       " msecs"))
     ret__6137__auto__)
    

    and functions were called in this order: (System/nanoTime), (Thread/sleep 1000) and other (System/nanoTime), which returns correct time.