2011-05-26 42 views
2

這是我的第一個Clojure宏 - 我是一個超級菜鳥。用於字符串模板替換的Clojure宏

昨天我posted並精煉了一個字符串模板替換函數。有幾個人建議在編譯時可以更換密鑰。這是我第一次嘗試:

(defn replace-templates* 
    "Return a String with each occurrence of a substring of the form {key} 
    replaced with the corresponding value from a map parameter. 
    @param str the String in which to do the replacements 
    @param m a map of template->value 
    @thanks kotarak https://stackoverflow.com/questions/6112534/ 
    follow-up-to-simple-string-template-replacement-in-scala-and-clojure" 
    [^String text m] 
    (let [builder (StringBuilder.)] 
    (loop [text text] 
     (cond 
     (zero? (count text)) 
     (.toString builder) 

     (.startsWith text "{") 
     (let [brace (.indexOf text "}")] 
      (if (neg? brace) 
      (.toString (.append builder text)) 
      (if-let [[_ replacement] (find m (subs text 1 brace))] 
       (do 
       (.append builder replacement) 
       (recur (subs text (inc brace)))) 
       (do 
       (.append builder "{") 
       (recur (subs text 1)))))) 

     :else 
     (let [brace (.indexOf text "{")] 
      (if (neg? brace) 
      (.toString (.append builder text)) 
      (do 
       (.append builder (subs text 0 brace)) 
       (recur (subs text brace))))))))) 

(def foo* 42) 
(def m {"foo" foo*}) 

(defmacro replace-templates 
    [text m] 
    (if (map? m) 
    `(str 
     [email protected](loop [text text acc []] 
     (cond 
      (zero? (count text)) 
      acc 

      (.startsWith text "{") 
      (let [brace (.indexOf text "}")] 
      (if (neg? brace) 
       (conj acc text) 
       (if-let [[_ replacement] (find m (subs text 1 brace))] 
       (recur (subs text (inc brace)) (conj acc replacement)) 
       (recur (subs text 1) (conj acc "{"))))) 

      :else 
      (let [brace (.indexOf text "{")] 
      (if (neg? brace) 
       (conj acc text) 
       (recur (subs text brace) (conj acc (subs text 0 brace)))))))) 
    `(replace-templates* ~text m))) 

(macroexpand '(replace-templates "this is a {foo} test" {"foo" foo*})) 
;=> (clojure.core/str "this is a " foo* " test") 
(println (replace-templates "this is a {foo} test" {"foo" foo*})) 
;=> this is a 42 test 
(macroexpand '(replace-templates "this is a {foo} test" m)) 
;=> (user/replace-templates* "this is a {foo} test" user/m) 
(println (replace-templates "this is a {foo} test" m)) 
;=> this is a 42 test 

有沒有更好的方法來編寫這個宏?特別是每個值的擴展版本都沒有獲得命名空間限定。

+0

用'(地圖?m)'好主意。 :) – kotarak 2011-05-26 20:35:53

回答

1

我會盡量減少重複的東西。我調整了函數以使用累加器的宏觀方法,並讓replace-templates*通過(apply str ...)完成其餘部分。這樣就可以重新使用宏中的函數。

(defn extract-snippets 
    [^String text m] 
    (loop [text  text 
     snippets []] 
    (cond 
     (zero? (count text)) 
     snippets 

     (.startsWith text "{") 
     (let [brace (.indexOf text "}")] 
     (if (neg? brace) 
      (conj snippets text) 
      (if-let [[_ replacement] (find m (subs text 1 brace))] 
      (recur (subs text (inc brace)) (conj snippets replacement)) 
      (recur (subs text 1)   (conj snippets \{))))) 

     :else 
     (let [brace (.indexOf text "{")] 
      (if (neg? brace) 
      (conj snippets text) 
      (recur (subs text brace) (conj snippets (subs text 0 brace)))))))) 

(defn replace-templates* 
    [text m] 
    (apply str (extract-snippets text m))) 

(defmacro replace-templates 
    [text m] 
    (if (map? m) 
    `(apply str ~(extract-snippets text m)) 
    `(replace-templates* ~text ~m))) 

注意:在您的宏中,您沒有取消引用m。所以它只能起作用,因爲你之前已經確定了它。它不會與(let [m {"a" "b"}] (replace-templates "..." m))

+0

感謝您的重構。我正在閱讀「Let Over Lambda」(Doug Hoyte) - 這都是關於Common Lisp宏的。還要感謝'〜m'上的答案。我早些時候嘗試過,但由於我的宏中的其他問題,沒有注意到這是「正確的」答案。 – Ralph 2011-05-26 20:48:22

0

更改(defn m {"foo" foo*})(def m {"foo" foo*})它似乎工作。

+0

是的,我已經找到了一個:-)。 – Ralph 2011-05-26 17:30:45