1
votes

I am very new to Clojure and completely new to macro system. I am writing a task management system in clojure where I send a piece of clojure code as EDN from one node to another.

To make things less messy, I created a macro called deftask where I associate a name with some code that I send as EDN to another node which evaluates and executes it.

(defmacro deftask                                                                                                                                                                   
 "Associates identifier with edn form of the code"                                                                                                                                  
 [id task]                                                                                                                                                                          
  `(def ~id (pr-str '~task)))                                                                                                                                                       

(deftask test-task                                                                                                                                                                  
  (tasks.test.task/execute "World!"                                                                                                                                                   
                           10                                                                                                                                                       
                           (ƒ [arg]                                                                                                                                                 
                              (println "Hello " arg))                                                                                                                               
                           (str "Successfully printed message " 10 " times."))) 

on the subscriber, this edn is converted to code, and then executed. However I need to use require and include tasks.test.task other wise eval gets an exception while trying to understand tasks.test.task.

Is there a better way to define the macro, so that I can pass full qualified name of a function without having such trouble ?

Here is my code at the subscriber side:

(defn execute-edn-expression                                                                                                                                                        
  "Evaluates and runs an expression written in Clojure EDN format"                                                                                                                  
  [edn]                                                                                                                                                                             
  (try                                                                                                                                                                              
    (eval edn)                                                                                                                                                                      
    (catch Exception ex                                                                                                                                                             
      (println "Ex: " (.getMessage ex)))))

whenever I don't use use require to include tasks.test.task there, catch block executes and the message I get is this:

Ex: tasks.test.task

Thats it!

2
what does exception say?hsestupin
check my update. the exception just shows a message with name of the namespace.Amogh Talpallikar
why don't you define the code chunk with the needed namespace requires inside?guilespi
For anyone wondering: the exception is a ClassNotFoundException. And yes, you have to add a (require 'tasks.test.task) either to the code sent by your producer or before evaluation of the form in your consumer. In both cases you can probably create a list of namespaces-to-require programmatically by recursively walking the code (or using zippers) and finding all fully qualified symbols.xsc
@GuillermoWinkler: my problem is if the subscriber can respond to 100 different task calls, i will have to include the namespace everywhere.Amogh Talpallikar

2 Answers

1
votes

In order to avoid having to require every possible namespace in your server handling code, you can add the require on the code chunk sent to the server.

Something like this:

(deftask test-task    
  (do    
    (require 'task.test.task)                                                                                                                                                          
    (tasks.test.task/execute "World!"                                                                                                                                                   
                           10                                                                                                                                                       
                           (ƒ [arg]                                                                                                                                                 
                              (println "Hello " arg))                                                                                                                               
                           (str "Successfully printed message " 10 " times."))))

So each tasks loads the not already loaded namespace it needs.

You can even modify your deftask macro and receive as first parameter the namespaces to load.

(deftask test-task        
      ['task.test.task 'other.namespace]                                                                                                                                                          
      (tasks.test.task/execute "World!"                                                                                                                                                   
                               10                                                                                                                                                       
                               (ƒ [arg]                                                                                                                                                 
                                  (println "Hello " arg))                                                                                                                               
                              (str "Successfully printed message " 10 " times.")))

Or if using fully namespace qualified functions with a deep walk macro automatically detect the namespaces to load.

1
votes

First of all: I hope you know that an application like that is very vulnerable since you basically allow arbitrary code to be executed. You might thus want to check out sandboxing strategies like those implemented in clojail.

Now, building on top of Guillermo's answer (at the point where I started typing, at least), you could actually generate a list of namespaces to require by traversing the form supplied to execute-edn-expression and collecting those symbols that have fully-qualified names:

(defn collect-namespaces
  [form]
  (cond (and (symbol? form) (namespace form)) 
          [(symbol (namespace form))]
        (and (sequential? form) (not= (first form) 'quote)) 
          (distinct (mapcat collect-namespaces form))
        :else []))

Then you could do something like this (on the client side!) before calling eval in your original code:

(apply require (collect-namespaces edn))

And all required namespaces should be loaded.