【问题标题】:How to simplify some code using defmacro如何使用 defmacro 简化一些代码
【发布时间】:2013-07-30 15:20:10
【问题描述】:

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

教科书里的简单宏例子我都懂,但我自己不会写。

这是我的代码:

 (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,拖曳功能工作正常。我不关心变量这是代码的一部分。

【问题讨论】:

  • 使用宏去除样板代码,使用函数去除重复代码

标签: macros clojure lisp


【解决方案1】:

我会使用 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 %))

【讨论】:

  • 你的代码一样简洁,除了 (take 3 %) 子句只取 % 的前 3 个成员,这里的顺序不正确。所以我修改了函数 gen-word 来解决这个问题。
  • 抱歉,我没有看到您的 (nth vvv 0), (nth vvv 2)... 中的顺序不是连续的。如果 gen-word 现在很乐意按顺序使用 vvv 的前三个,我将保持原样。或者,如果您有任何意愿,我可以更改它。
【解决方案2】:

user1944838 在这里是正确的,因为您不需要宏,并且由于宏生成不需要它们的代码,因此在某些情况下使用起来稍微困难一些(您不能将其传递给 map 或 @例如 987654322@),在实践中使用函数更可取。然而,了解如何正确编写宏非常重要。

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

(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-open等“with-*”宏中很常见,除了这里我只采用一种绑定形式来保持代码更小。
  • syntax-quote 中的rdr#line# 符号是一种称为“auto 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]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-07
    • 2021-08-03
    • 2017-12-19
    • 1970-01-01
    • 2019-08-20
    • 1970-01-01
    • 2014-11-01
    • 1970-01-01
    相关资源
    最近更新 更多