我对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
多数民众赞成!
答案 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))
应该加载所有必需的命名空间。