Is there a clean way to add functions to a dynamically created namespace?
Asked Answered
E

1

11

I am creating a noir webapp, and I need to dynamically create new views and models. I've been following the noir examples, in which the view and the controller for a resource have separate namespaces, and I've found it to be a very clean approach.

In keeping with this, I need to be able to dynamically create new namespaces corresponding to views and models, and then intern the appropriate functions in them. My idea was to have macros specified in a separate namespace which, when called in the new namespace, would provide the appropriate routes/partials/whatever.

For example (forgive my first defmacro):

(ns project.views.proto
  (:use noir.core
        hiccup.core
        hiccup.element
        hiccup.form))

(defmacro def-all-page
  [path]
  `(defpage ~path []
     (html
      [:h1 "Ya'll here"])))

is called from...

(ns project.proto
   (:use [clojure.contrib.with-ns :only [with-ns]])

(create-ns 'foo)
(intern 'foo 'path "path")  ; In reality, the path is dynamic which is why I intern it
(with-ns 'foo
    (clojure.core/refer-clojure)
    (use 'noir.core
         'hiccup.core
         'hiccup.element
         '[project.views.proto :only [def-all-page]])

    (def-all-page path)

However, calling this from within my new namespace gives me a NullPointerException. I'd greatly appreciate any help, and whether or not there is a better approach. Like, just using refer for a namespace which contains all the necessary definitions?

First post, and I don't think it's a repeat of this. Thanks!

Excretion answered 22/4, 2012 at 20:4 Comment(11)
Would you consider editing your post, and denoting your name space and some sample Clojure code that invokes the macro? I'd like to see in what context it is used. tnxHandbreadth
I hope that's a bit more helpful.Excretion
The error I'm getting is Unable to resolve symbol: with-ns in this context, compiling, but I don't know if that means you can use with-ns outside a function or macro.Handbreadth
After reading the ns docs a little more, I'm not sure why you're using both create-ns and intern. intern will add to the name space you already declared, but I still don't know why with-ns shows up as an unresolvable symbol, unless you're meant to use it in a function.Handbreadth
Ahhh sorry, I neutered my ns dec when I typed it in: I use clojure.contrib.with-ns. I'll edit my post to reflect this when I get to my computer. As for intern, the posted code doesn't reflect this, but the path variable is outside of the created namespace so it needs to be explicitly added. At least, that was the justification I gave myself...Excretion
The code in the clojure-contrib is deprecated and unsupported: dev.clojure.org/display/design/Where+Did+Clojure.Contrib+Go I'd suggest not using clojure.contrib.with-ns in that case.Flyover
That's good to know... and perhaps a different library will point me to a more idiomatic way to perform this task. ThanksExcretion
Did you solve this issue? Its an interesting problem.Angeloangelology
Nope, and that project is gaining dust now. That being said, I'm still very interested in the answer also...Excretion
I'm actually curious about this issue because I can't seem to reproduce the problem in noir 1.3-beta1 and up. Do you have the code published somewhere?Idyllist
@Idyllist I wish -- it and the laptop it was on are long gone. Are you working on something similar? As for the noir version, I just checked the github repo and found that a) it's deprecated, and b) the most recent tagged version is 1.2.2 -- So I'm guessing that I was using something prior to this 1.3-beta1. Also, I was probably using clojure 1.2. Sorry I don't have more info, and good luck!Excretion
P
1

First of all, this question has become a bit outdated. Both Noir and Clojure have evolved over the last year. For clarity's sake i'll take Noir out of the equation and try to answer your question about dynamically creating functions using macros.

Follow along at the REPL:

$ lein repl
user=> (in-ns 'foo)
#<Namespace foo>
foo=> (clojure.core/refer-clojure)
nil
foo=> (defmacro say-hello-to
 #_=>           [name]
 #_=>           `(defn ~(symbol (str "hello-" name))
 #_=>                  []
 #_=>                  ~(str "hello: " name)))
#'foo/say-hello-to

Here we create a namespace 'foo' that contains a macro for defining 'hello-yourname' functions. Let's create another namespace:

foo=> (in-ns 'bar)
#<Namespace bar>
bar=> (clojure.core/refer-clojure)
nil
bar=> (refer 'foo :only '[say-hello-to])
nil
bar=> (say-hello-to "tom") 
#'bar/hello-tom
bar=> (say-hello-to "jerry") 
#'bar/hello-jerry

Let's see if these actually work:

bar=> (hello-tom)
"hello: tom"
bar=> (hello-jerry)
"hello: jerry"

I think this is pretty close to your original example.

Hope this helps!

Propylaeum answered 27/10, 2013 at 22:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.