如何使用defmacro简化一些代码

时间:2013-07-30 15:20:11

标签: macros clojure lisp

我写了两个像这样的函数,但正如你所看到的那样大部分是相同的,所以我想编写一个宏来简化它们。

我理解教科书中的简单宏示例,但我不知道如何编写自己的。

这是我的代码:

 (defn load-dict
       ;  "Load database from a txt file previous saved"
       [fname]
       (with-open [rdr (io/reader fname)]
                  (doseq [line (line-seq rdr)]
                         (println line)
                         (def vvv (clojure.string/split line #"\s"))
                         ;(println (str "count of vvv is " (count vvv)))
                         (if (< 1 (count vvv))
                           (add- dict (gen-word (nth vvv 0) (nth vvv 2) (nth vvv 1))))
                         )))
 (defn load-article
       ;  "Load article from a txt file"
       [fname]
       (with-open [rdr (io/reader fname)]
                  (doseq [line (line-seq rdr)]
                         (println line)
                         (def vvv (clojure.string/split line #"\s"))
                         ;(println (str "count of vvv is " (count vvv)))
                         (if (< 1 (count vvv))
                           (add- article vvv ))
                         )))

我应该写一个像:

的宏
(defmacro load- [target fname &expr)
  `(... 
    (add- ~target expr)))

我其实不知道怎么写这样的宏。我只是讨厌重复的代码。

PS,拖曳功能正常。我不关心变量这是代码的一部分。

2 个答案:

答案 0 :(得分:5)

我会使用let块而不是def。使用def将绑定var并在命名空间中定义vvv。实际上并不需要宏。您可以像这样简化代码:

(defn load-from
   "Load database from a txt file previous saved"
   [fname load-fn]
   (with-open [rdr (io/reader fname)]
              (doseq [line (line-seq rdr)]
                     (println line)
                     (let [vvv (clojure.string/split line #"\s")]
                       (when (< 1 (count vvv))
                         (load-fn vvv))))))

并像这样调用它

(load-from "myfile.txt" #(add- dict (apply gen-word (take 3 %))))
(load-from "myfile.txt" #(add- article %))

答案 1 :(得分:3)

user1944838在这里是正确的,因为你不需要宏,并且由于宏生成不需要它们的代码,在某些情况下稍微难以处理(你不能将它传递给map或者例如apply),在实践中优选使用函数。然而,了解如何正确编写宏非常重要。

我会把它写成一个模板宏,它将你传给它的名字绑定到每个单词,然后调用传递给宏的主体,然后通过符号名称使用该单词。

(defmacro with-loaded-article                                                                                                                                             
  [[name-for-line fname] & body]                                                                                                                                          
  `(with-open [rdr# (io/reader ~fname)]                                                                                                                                   
     (doseq [line# (line-seq rdr#)]                                                                                                                                       
       (println line#)                                                                                                                                                    
       (let [~name-for-line (clojure.string/split line# #"\s")]                                                                                                           
         ~@body)))) 
  • [name-for-line fname]表达式将第一个参数解构为单个“绑定表单”,该表单将用于生成符号及其将解析的值。这种格式在“with- *”宏中很常见,例如with-open,除了这里我只采用一种绑定形式来保持代码更小。
  • 语法报价中的rdr#line#符号是语法报价的一个特征,称为“自动gensyms”,导致以#结尾的语法引号中的任何符号被唯一替换,虽然在结果表达式中使用了一致的符号。
  • ~@是语法引用的拼接 - 非引用功能,在这种情况下会导致插入body而不会在其周围添加( )

我们可以看到macroexpand-1pprint提示如何扩展:(use 'clojure.pprint)

hello.core> (pprint (macroexpand-1 
                      `(with-loaded-article [line "tmp.txt"] (println line))))                                                                                 
(clojure.core/with-open                                                                                                                                                   
 [rdr__6337__auto__ (clojure.java.io/reader "tmp.txt")]                                                                                                                   
 (clojure.core/doseq                                                                                                                                                      
  [line__6338__auto__ (clojure.core/line-seq rdr__6337__auto__)]                                                                                                          
  (clojure.core/println line__6338__auto__)                                                                                                                               
  (clojure.core/let                                                                                                                                                       
   [hello.core/line (clojure.string/split line__6338__auto__ #"\s")]                                                                                                      
   (clojure.core/println hello.core/line))))                                                                                                                              

当我们运行生成的代码时,我们将文件的行作为序列:

hello.core> (with-loaded-article [line "tmp.txt"] (println line))                                                                                                         
hello world                                                                                                                                                               
[hello world]                                                                                                                                                             
world hello                                                                                                                                                               
[world hello]                                                                                                                                                             
and internet hello as well                                                                                                                                                
[and internet hello as well]