tags:

views:

171

answers:

2
+5  Q: 

let inside cond

Hello. I'm working with clojure and while I've dabbled with lisps before, I'm having trouble finding a clean way to nest let statements in cond statements. For example, consider the following function:

(defn operate-on-list [xs]
  (let [[unpack vector] (first xs)]
    (cond
      [(empty? xs) 'empty
       unpack vector
       :else (operate-on-list (rest xs))])))

It's a pretty standard recursive operation on a list, but it needs to do some work on the first element in the list before it works with the contents. The issue, of course, is that the list may be empty.

In this example, it wouldn't be hard to change unpack to ((first xs) 0) and vector to ((first xs) 1), but this quickly gets ugly if more work needs to be done on (first xs).

Is there any way to effectively use a let statement part-way through a cond?

Thanks.

-Nate

+2  A: 

Sort of like this, with a let inside the scope of the cond?

(defn operate-on-list [list]
  (let [ el_first (first list) ]
    (cond
      (nil? el_first) (println "Finished")
      :else (do 
       (let [ list_rest (rest list) ]
                (println el_first)  
                (operate-on-list list_rest))))))

(operate-on-list '(1 2 3))

The output is:

1
2
3
Finished
Duncan Bayne
Your `do` is superfluous.
Brian Carper
@Brian Carper: you're right; you can simply omit the do. That was an Elisp hangover on my part (in particular, progn).
Duncan Bayne
+7  A: 

In cases like these, you're best off using if-let:

(defn operate-on-list [xs]
  (if-let [[unpack v] (first xs)]
    (cond
      unpack v
      :else  (operate-on-list (rest xs)))))

This code walks the given list seq-able (list, vector, array...) of vectors and returns the second element of the first vector whose first element is true (meaning not false or nil). nil is returned if no such vector is found.

Note that vector is a built-in function, so I've chosen v as the variable name, just in case the need to use the function in the body arises in the future. More importantly, you're using too many brackets in your cond syntax; fixed in this version.

UPDATE: Two additional things worth noting about if-let:

  1. The way if-let works, if (first xs) happens to be nil (false would be the same), the destructuring binding never takes place, so Clojure won't complain about not being able to bind nil to [unpack v].

  2. Also, if-let accepts an else clause (in which you can't refer to the variables bound in if-let bindings vector -- though if you're in the else clause, you know they where false or nil anyway).

Michał Marczyk
See also `when-let` which is more idiomatic when you have only one branch.
kotarak
A second note: be aware that `if-let` works here, because the list is supposed to contain vectors. In general `(when-let [x (first s)] ...)` is *not* a substitute for `(when-let [s (seq s)] (let [f (first s)] ...))`.
kotarak
@kotarak: Good point about `when-let`, thanks for bringing it up! As for the second note, however, `first` is supposed to call `seq` on its argument (that's what the docstring promises; see src/jvm/clojure/lang/RT.java in Clojure's sources for the definition of the involved primitive, which happily lives up to the promise), so `(when-let [x (first s)] ...)` can in fact be substituted for the second construct you show. The fact that the list is composed of vectors enters into the picture because of the destructuring bind (binding `(first s)` to `[unpack v]`), but that's another matter entirely.
Michał Marczyk