guarding an expression
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?
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.
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.
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
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.