Two Guys Arguing

guarding an expression

Posted in clojure by youngnh on 02.01.11

Oftentimes, I want to make some computation, apply the result to a predicate, and if it passes, return that result. If the predicate does not succeed, I usually want to return some other value. I’ve run into this situation before, with no satisfying resolution. My code usually ends up looking like so:

(let [value (some-computation x y z)]
  (if (check? value)
    value
    some-other-default-value))

Which is overly verbose, even for Clojure, if you ask me. There’s a let and value appears 3 times for a fairly straightforward idiom. I think what I’d like to write instead is:

(guard check?
  (some-computation x y z)
  some-other-default-value)

The following macro does the trick of expanding to the verbose form I’ve been writing:

(defmacro guard [pred then else]
  `(let [x# ~then]
     (if (~pred x#)
       x#
       ~else)))

Now, the awkward thing about the above is that unlike an if-statement, the order in which you read things is not the order in which they get executed in. Reading it, the execution happens on line 2 first, line 1 second, and then possibly on line 3. My question to the blogosphere is, does Clojure already have something like this lurking in a lib somewhere? Or is there a blindingly obvious solution to this that I’m overlooking?

About these ads

4 Responses

Subscribe to comments with RSS.

  1. Alex Miller said, on 02.01.11 at 8:56 pm

    I don’t know of anything that does exactly this – seems like if-let has some of the feel and the :when guard in for comprehensions suggest some other patterns.

    What about starting from if-let and adding a :when guard (which is evaled with the let val):

    (defmacro guard-let
    [[v f _ guard?] then else]
    `(let [~v ~f]
    (if (~guard? ~v)
    ~then
    ~else)))

    (defn add-or-even [x y]
    (guard-let [val (+ x y) :when odd?] val :even))

    (add-or-even 1 2)
    > 3
    (add-or-even 2 2)
    >:even

    Could easily do a form that implicitly named val and returned it when the guard is true so it would look like: (guard-let [(+ x y) :when odd?] :even) and then you could let go of the let binding form and just do something like: (guard (+ x y) :when odd? :even) which is pretty much what you have but orders the computation better and echoes the (for) style :when guard.

  2. Paul deGrandis (ohpauleez) said, on 02.02.11 at 11:51 am

    I usually use if and if-let to achieve this:
    (if-let [n-shared (if (empty? shared-items) false (count shared-items))]
    (let [...])
    0)

    I check for a condition with if, within the if-let, if I see the condition, I return false, which hits the false return condition. Otherwise I bind and move into the next let block.

  3. Jacek Laskowski said, on 02.11.11 at 3:58 pm

    What’s wrong with the following?

    user=> (defn check? [v] (= v 5))
    #’user/check?
    user=> (defn some-computation [x y z] (+ x y z))
    #’user/some-computation
    user=> (filter check? (list (some-computation 1 3 1) 7))
    (5)
    user=> (first (filter check? (list (some-computation 1 3 1) 7)))
    5

    • youngnh said, on 02.13.11 at 2:08 pm

      This solution certainly looks like it would work.

      After pondering it for a while, though, I think I would pass on this solution. It’s doing more work than necessary. First, it has to be wrapped up in a new list, which has a (possibly minimal) cost, and finally you have to unwrap it from the list filter returns using first, which also has a (probably very minimal) cost.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.