On Mon, Apr 07, 2014 at 04:08:03AM -0700, Peter West wrote:
> I'm trying to understand the difference between two alternatives in the 
> following code that reads from a resource file.
>
> (defn vcf-res-reader
>   [res]
>   (->> res
>        io/resource
>        io/reader))
>
> (defn lines-only
>   [varname prom resource]
>   (with-open [r (vcf-res-reader resource)
>               ; alternative 1
>               ;lineseq (line-seq r)
>               ]
>     ; alternative 2
>     (def lineseq (line-seq r))
>     (eval `(def ~(symbol varname) lineseq))
>     @prom))
>
> (defn lazy-lines
>   [varname prom resource]
>   (future
>     (lines-only varname prom resource)
>     ))
>
>
> As the code stands, with alternative 2 enabled, the eval setting a named 
> var to the value of "linseq" works as intended. However, if I disable 
> alternative 2 and enable alternative 1, and I attempt to access the named 
> var ("cards" in this case), I get
> #<Unbound Unbound: #'vcf.core/cards>

You have a few problems with your code as it stands. I'll look at each
of the two alternatives separately.

First, alternative 1:

> (defn lines-only
>   [varname prom resource]
>   (with-open [r (vcf-res-reader resource)
>               lineseq (line-seq r)]
>     (eval `(def ~(symbol varname) lineseq))
>     @prom))

Your issue here is that the symbol "lineseq" in the eval form doesn't
have a name to refer to. You do have a local binding for lineseq, but
it's not visible to the eval:

  (let [x 10] (eval 'x)) ;=> Unable to resolve symbol: x

The lineseq binding should really be made in a "let", too, rather than a
with-open. The result of a line-seq can't be closed, so it's not really
sensible to put it in a "with-open".

Now, on to alternative 2:

> (defn lines-only
>   [varname prom resource]
>   (with-open [r (vcf-res-reader resource)}
>     (def lineseq (line-seq r))
>     (eval `(def ~(symbol varname) lineseq))
>     @prom))

This time, you do have something for "lineseq" to refer to! The "def"
special form has created a global binding for "lineseq". Now, at the top
level of your application you can lookup "lineseq" and get a value, but
this isn't good, because your function now modifies the global scope,
just to hold a temporary value.




There are two ways for you to resolve this. What you're writing sounds a
little bit like it should be a macro, so you could write it as such:

  (defmacro lines-only [varname prom resource]
    `(with-open [r# (vcf-res-reader ~resource)]
       (let [lineseq# (line-seq r#)]
         (def ~(symbol varname) lineseq#)
         @~prom)))

(I think that's right, but I've not actually tested it.)

Alternatively, you could write it as a function and use "intern":

  (defn lines-only [varname prom resource]
    (with-open [r (vcf-res-reader resource)]
      (let [lineseq (line-seq r)]
        (intern *ns* (symbol varname) lineseq)
        @prom)))

I'm less confident about this approach, but it should work.

All this being said: it's generally a bad idea to have a macro/function
modify the global scope without it being very clear that it's doing so.
I usually try to have my macro names start with "def" if they're
modifying the global scope (because they're "defining" something is my
logic).

Attachment: signature.asc
Description: Digital signature

Reply via email to