2

I'm finding myself writing a function that passes every variable bound in the enclosing let expression. The simplified form of this scenario, in psuedocode, is

(let [a 1 b 2 c 3 d 4 e 5 f 6]
  (do-something a b c d)
  (f a b c d e f g)
  (compute-another-thing a c))

whereas I want to write something more like...

(let env-name [a 1 b 2 c 3 d 4 e 5 f 6]
   (do-something (take 4 env-name))
   (f env-name)
   (compute-another-thing a c))

That is, does the clojure core (or something external and reliable) provide a let variant that gathers all bindings into a collection with a name?

seewalker
  • 1,123
  • 10
  • 18

2 Answers2

2

Destructuring will get you part of the way there:

(let [[a b c d e f :as env-name] [1 2 3 4 5 6]]
   (apply do-something (take 4 env-name))
   (apply f env-name)
   (compute-another-thing a c))

Note you have to use apply when calling functions using env-name.

For something more elegant (like your proposed form), I think you would have to write your own macro.

Update: Here's a (very-lightly tested) macro that can handle simple cases:

(defmacro let-as 
  [as-bind-name bindings & body]
  (let [lhs-bindings (map first (partition 2 bindings))]
    `(let ~bindings
       (let [~as-bind-name [~@lhs-bindings]]
             ~@body))))

This should do what you want so long as the left-hand side of your normal let-bindings are simple symbols (ie don't use destructuring).

Some REPL interactions to illustrate:

core> (let-as env-name [a 1 b 2 c 3 d 4 e 5 f 6] (print env-name) (apply + env-name))
[1 2 3 4 5 6]
21

Some destructuring is well-behaved:

core> (let-as env-name [[a b] [1 2] [c d] [3 4]] (print env-name) (map first env-name))
[[1 2] [3 4]]
(1 3)

Other destructuring is not:

core> (let-as env-name [{:keys [a b]} {:a 1 :b 2}] (print env-name) (+ a b))
[{:keys [1 2]}]
3

This could be remedied, but the macro would have to be more complex

YosemiteMark
  • 675
  • 1
  • 6
  • 13
  • Thanks!, destructuring fits the application I have in mind. If nobody is aware of an existing solution and nobody responds with a cool macro, I'll mark yours as the accepted answer. – seewalker Aug 25 '14 at 21:35
  • I've updated my answer with a (lightly-tested) macro that handles the simple cases, hope it proves useful! – YosemiteMark Aug 26 '14 at 00:49
1

Destructuring is probably the best solution here, but just for interest's sake, Fogus has got a lexical-context macro in his evalive project. It looks like this:

(defmacro lexical-context
  []
  (let [symbols (keys &env)]
    (zipmap (map (fn [sym] `(quote ~sym))
                 symbols)
            symbols)))

(let [a 1]
  (let [b 2]
    (lexical-context))) ; => {a 1, b 2}

It uses the special &env variable available to macros, so you don't even need to use a different form of let.

Daniel Neal
  • 4,165
  • 2
  • 20
  • 34