Skip to content

Instantly share code, notes, and snippets.

@travis
Forked from sritchie/macro.md
Created January 25, 2012 14:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save travis/1676543 to your computer and use it in GitHub Desktop.
Save travis/1676543 to your computer and use it in GitHub Desktop.

A little Clojure challenge inspired by Let over Lambda.

Write a macro (you can try a function, but it's impossible) that accepts four arguments:

  1. an expression that returns a number
  2. something to return if that number's negative
  3. something to return if that number's zero
  4. something to return if that number's positive

Here's the signature: (defmacro nif [expr neg zero pos] ...)

To pass my test, the following form should print "pos!" and return "pos.", with no other side effects:

(nif 10
      (do (println "Negative!") "negative.")
      (do (Thread/sleep 1000000) "zero.")
      (do (println "pos!") "pos."))

May the best lisper win!

@travis
Copy link
Author

travis commented Jan 25, 2012

Perhaps not the most elegant solution, but it should get the job done:

(defmacro nif [expr neg zero pos]
  `(let [result# ~expr]
     (if (> result# 0)
         ~pos
         (if (< result# 0)
           ~neg
           ~zero))))
user> (nif 10
      (do (println "Negative!") "negative.")
      (do (Thread/sleep 1000000) "zero.")
      (do (println "pos!") "pos."))
pos!
"pos."

@sritchie
Copy link

Boom! This is great. Couple of style comments, if you're interested:

For nested if statements like this, especially as I add more clauses, I like to use cond. Cond takes pairs of <test-expr>, <result> and returns the result for the first truthy test-expr it sees:

(defmacro nif [expr neg zero pos]
  `(let [result# ~expr]
     (cond (> result# 0) ~pos
           (< result# 0) ~neg
           :else          ~zero)))

Beyond that, you might use the clojure.core/neg? and clojure.core/pos? functions vs comparing to zero (totally identical to your solution):

(defmacro nif [expr neg zero pos]
  `(let [result# ~expr]
     (cond (pos? result#) ~pos
           (neg? result#) ~neg
           :else           ~zero)))

Nice job, man. I'll see if I can come up with some tougher ones to toss out there for next time.

@travis
Copy link
Author

travis commented Jan 25, 2012

ah, pos and neg are great. does cond do lazy evaluation like the ifs do?

@travis
Copy link
Author

travis commented Jan 25, 2012

ah, indeed it does! right, cause duh:

(defmacro cond
  "Takes a set of test/expr pairs. It evaluates each test one at a
  time.  If a test returns logical true, cond evaluates and returns
  the value of the corresponding expr and doesn't evaluate any of the
  other tests or exprs. (cond) returns nil."
  {:added "1.0"}
  [& clauses]
    (when clauses
      (list 'if (first clauses)
            (if (next clauses)
                (second clauses)
                (throw (IllegalArgumentException.
                         "cond requires an even number of forms")))
            (cons 'clojure.core/cond (next (next clauses))))))

Built On Ifs - love it

@sritchie
Copy link

Yup, cond is actually a macro that expands out into the same if statement you had:

(cond (pos? x) "pos"
      (neg? x) "neg"
      :else "zero")

;; expands out to:

(if (pos? x)
  "pos"
  (if (neg? x)
    "neg"
    (if :else
      "zero"
      nil)))

@sritchie
Copy link

:else is just a convention, since keywords are always truthy. This is equally valid, but unclear:

(cond (pos? x) "pos"
      (neg? x) "neg"
      "FALSE!" "zero")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment