如何在Clojure中创建一个defxyz宏?

时间:2013-11-18 11:03:54

标签: macros clojure

我对Clojure很新,对宏系统来说是全新的。 我在clojure中编写一个任务管理系统,我将一段clojure代码作为EDN从一个节点发送到另一个节点。

为了减少麻烦,我创建了一个名为deftask的宏,我将一个名称与我作为EDN发送的一些代码关联到另一个评估和执行它的节点。

(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."))) 
订阅者

,此edn转换为代码,然后执行。 但是,我需要使用require并包含tasks.test.task其他明智的eval在尝试理解tasks.test.task时获得异常。

有没有更好的方法来定义宏,以便我可以传递函数的完全限定名称而不会有这样的麻烦?

以下是我在订阅方的代码:

(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)))))

每当我不使用use require包含tasks.test.task时,catch块执行,我得到的消息就是:

Ex:tasks.test.task

多数民众赞成!

2 个答案:

答案 0 :(得分:1)

为了避免服务器处理代码中的require每个可能的命名空间,您可以在发送到服务器的代码块上添加require

这样的事情:

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

因此,每个任务都会加载它所需的尚未加载的命名空间。

您甚至可以修改deftask宏并作为第一个参数接收要加载的命名空间。

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

或者,如果使用带有深度遍历宏的完全名称空间限定函数,则会自动检测要加载的名称空间。

答案 1 :(得分:1)

首先:我希望您知道像这样的应用程序非常容易受到攻击,因为您基本上允许执行任意代码。因此,您可能希望查看沙盒策略,例如clojail中实现的沙盒策略。

现在,建立在Guillermo的答案之上(至少在我开始输入的时候),您实际上可以通过遍历提供给execute-edn-expression的表单并收集那些符号来生成要求的命名空间列表。拥有完全合格的名字:

(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 []))

然后你可以在原始代码中调用eval之前做这样的事情(在客户端!):

(apply require (collect-namespaces edn))

应该加载所有必需的命名空间。