Joe Corneli
Joe Corneli

Reputation: 662

working with non-namespaced symbols in clojure

Here's a working minimal example showing how Clojure can handle non-namespaced symbols:

(defmacro simple-macro [s]
  (name `~s))

(str "And the answer is "
     (simple-macro v1))

Now I'd like to do something more complicated. Inspired by this example:

(defn typical-closure []
  (let [names (atom [])]
    (fn [arg] (swap! names conj arg) @names)))

(def Q (typical-closure))
(Q 1)
(Q 2)
;; [1 2]

I now want to define a similar closure to take the names of undefined variables.

(defn take-names-fun []
  (let [names (atom [])]
    #((swap! names conj (simple-macro %)) (deref names))))

(def P (take-names-fun))
(P v1)

But this doesn't work as hoped; I get the error:

Unable to resolve symbol: v1 in this context

Is there a way to fix this so that we can add the name "v1" to the list of names defined above?

I tried using a macro instead (inspired by a syntax trick on page 21 of "Mastering Clojure Macros")... but this answer on ask.clojure.org says it doesn't make sense to define a closure over an atom in a macro.

(defmacro take-names-macro []
  (let [names (atom [])]
    `(fn [~'x] (swap! ~names conj (simple-macro ~'x)) (deref ~names))))

(def R (take-names-macro))

And indeed, I get another error here:

Can't embed object in code, maybe print-dup not defined:

However, there is no such restriction for using atoms inside defn. Maybe at the end of the day I need to put my symbols in a namespace...?

Upvotes: 2

Views: 209

Answers (3)

dorab
dorab

Reputation: 807

Not quite sure what it is that you're ultimately trying to accomplish.

But, since P is a function, it will always evaluate its arguments. So, if you pass it an undefined symbol, you'll get the error you got. Instead, you have to create a macro so that you can quote the undefined symbol (to stop the evaluation of the argument) and then pass that to P. Here is an example that does that.

user> (defn take-names-fun []
        (let [names (atom [])]
          (fn [arg] (swap! names conj (name  arg)))))
#'user/take-names-fun
user> (def P (take-names-fun))
#'user/P
user> (defmacro PM [s] `(P (quote ~s)))
#'user/PM
user> (PM v1)
["v1"]
user> (PM v2)
["v1" "v2"]
user> 

You might find the article on Evaluation in Clojure helpful.

Upvotes: 3

Gwang-Jin Kim
Gwang-Jin Kim

Reputation: 9865

@dorab's answer is nice. But you could also tell yourself: "When entering undefined variables into a function, I have to quote them to avoid evaluation of them!"

So, after:

(defn typical-closure []
  (let [names (atom [])]
    (fn [arg] (swap! names conj arg) @names)))

(def Q (typical-closure))

Do:

user=> (Q 'v1)
[v1]
user=> (Q 'v2)
[v1 v2]
user=> (Q 3)
[v1 v2 3]
user=> (Q 'v4)
[v1 v2 3 v4]
user=> 

In this way you don't need the macro and you can alternate between evaluated and not-evaluated arguments (undefined symbols).

Upvotes: 1

Justin Frost
Justin Frost

Reputation: 46

So with the way fn's are written in clojure there is unfortunately no way to get the name of the var being passed as a param from within the fn body.. Someone with more experience with the clojure src may be able to explain better why that is, my initial guess would be that it has something to do with keeping thread local scopes isolated and lazy.

But there's absolutely nothing stopping you from writing a macro that wraps other macros using your closure idea!

Here's an example of how something like that may be written: https://stackoverflow.com/a/11857444

Upvotes: 0

Related Questions