Respo 中的 defcomp Macro

Macro 有點相似編譯過程執行的函數, 固然它不是函數,
不熟悉的能夠先看看下面的文章瞭解一下 Clojure 的 Macro:
https://learnxinyminutes.com/...html

;; 宏能夠傳入參數。
(defmacro inc2 [arg]
  (list + 2 arg))

(inc2 2) ; -> 4

;; 不過,若是你嘗試配合使用引用列表,會致使錯誤,
;; 由於參數也會被引用。
;; 爲了不這個問題,clojure提供了引用宏的另外一種方式:`
;; 在`以內,你可使用~得到外圈做用域的變量。
(defmacro inc2-quoted [arg]
  `(+ 2 ~arg))

(inc2-quoted 2)

我在 Respo 裏遇到一個語法糖的問題, 據說要 Macro 才能解決.
Respo 裏定義組件的寫法挺長的, 包含了好幾層的嵌套函數和縮進:函數

(def comp-demo
  (create-comp :demo
    (fn [content] 
      (fn [cursor]
        (div
          {:class-name "demo-container"
           :style {:color :red}}
          (comp-text content nil))))))

不少代碼是重複的, 我想了下, 簡化之後甚至能夠這樣寫:post

(defcomp comp-demo [content]
  (div
    {:class-name "demo-container"
     :style {:color :red}}
    (comp-text content nil)))

本來我不知道有沒有辦法能作到, 畢竟 Respo 基於高階函數實現的,
固然, 對於用戶來講顯然是短的寫法更好記了,
除了沒有看到 cursor 定義可能會形成困擾以外, 能夠說是很好的寫法.code

後來看到社區其餘同窗的代碼, 發現是能夠作到的,
最終獲得的代碼是這樣的:htm

(defmacro defcomp [comp-name params & body]
  `(def ~comp-name
    (create-comp ~(keyword comp-name)
      (~'fn [~@params]
        (~'fn [~'cursor] ~@body)))))

要理解這個代碼, 須要對 Clojure 裏 Macro 的編寫有所瞭解
defmacro 這個語法能夠定義 Macro, 後面是名字和參數,
`() 表示括號的代碼都是符號, 不會被編譯器直接執行,
~comp-name 表示其中的 comp-name 是通過計算的, 並非符號類型或者說代碼,
~@body 跟上面相似, 可是加上 @ 以後, 代表內容是序列, 能夠被展開,
& 放在參數裏表示以後多個參數被摺疊到 body 變量上, 因此是序列,blog

爲了驗證這份代碼正常運行, 我用 boot repl 啓動一個 Clojure REPL:作用域

boot.user=> (defmacro defcomp [comp-name params & body]
       #_=>   `(def ~comp-name
       #_=>     (create-comp ~(keyword comp-name)
       #_=>       (~'fn [~@params]
       #_=>         (~'fn [~'cursor] ~@body)))))
#'boot.user/defcomp

而後嘗試展開符號:get

boot.user=> (macroexpand-1 '(defcomp comp-demo [content]
       #_=>     (div
       #_=>       {:class-name "demo-container"
       #_=>        :style {:color :red}}
       #_=>       (comp-text content nil))))
(def comp-demo (boot.user/create-comp :comp-demo (fn [content] (fn [cursor] (div {:class-name "demo-container", :style {:color :red}} (comp-text content nil))))))

macroexpand-1 這個函數能夠將符號格式的代碼進行一次展開,
這裏的 '() 跟前面的 `() 是相似的, 表示這裏都是代碼.
而後我把返回結果格式化一下:編譯器

(def comp-demo
  (boot.user/create-comp :comp-demo
    (fn [content]
      (fn [cursor]
        (div {:class-name "demo-container", :style {:color :red}}
        (comp-text content nil))))))

基本上是同樣的, 不過 create-comp 被帶上了命名空間,
固然這個代碼運行不了, 由於 create-comp 沒定義, 可是至少符號嘛, 還不會報錯.
fn 沒有被帶上命名空間, 是由於用了 ~'fn 寫法, 強行轉成了符合再轉回來.it

感興趣能夠扒更多文章看(雖然下面的文章我只看了一部分...)
http://lambdax.io/blog/posts/...
http://www.braveclojure.com/w...
https://aphyr.com/posts/305-c...

相關文章
相關標籤/搜索