【问题标题】:How to create a defxyz macro in Clojure?如何在 Clojure 中创建 defxyz 宏?
【发布时间】:2013-12-01 12:12:26
【问题描述】:

我对 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)))))

每当我不使用 require 在其中包含 tasks.test.task 时,catch 块就会执行并且我得到的消息是这样的:

例如:tasks.test.task

就是这样!

【问题讨论】:

  • 异常说什么?
  • 检查我的更新。该异常只显示一条带有命名空间名称的消息。
  • 为什么不在代码块中定义所需的命名空间需要呢?
  • 对于任何想知道的人:ClassNotFoundException 是个例外。是的,您必须将(require 'tasks.test.task) 添加到您的生产者发送的代码中,或者在评估您的消费者中的表单之前。在这两种情况下,您都可以通过递归遍历代码(或使用 zippers)并找到所有完全限定的符号,以编程方式创建一个需要命名空间的列表。
  • @GuillermoWinkler:我的问题是如果订阅者可以响应 100 个不同的任务调用,我将不得不在任何地方都包含命名空间。

标签: macros clojure


【解决方案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.")))

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

【讨论】:

    【解决方案2】:

    首先:我希望您知道这样的应用程序非常容易受到攻击,因为您基本上允许执行任意代码。因此,您可能想查看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))
    

    并且所有需要的命名空间都应该被加载。

    【讨论】:

    • 在我们的例子中,我们没有运行来自用户或任何第三方系统的任意代码,是的,我们正在考虑使用沙箱,但担心性能。无论如何,我们也在关注风暴项目。
    猜你喜欢
    • 2019-02-26
    • 2015-07-29
    • 1970-01-01
    • 2013-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-07
    • 2023-04-08
    相关资源
    最近更新 更多