加载中...

clojure macros


其中 Y=clojure macros

源代码下载: learnclojuremacros-zh.clj

和所有Lisp一样,Clojure内在的同构性使得你可以穷尽语言的特性,编写生成代码的子过程——“宏”。宏是一种按需调制语言的强大方式。

小心!可以用函数完成的事用宏去实现可不是什么好事。你应该仅在需要控制参数是否或者何时eval的时候使用宏。

你应该熟悉Clojure.确保你了解Y分钟学Clojure中的所有内容。

  1. ;; 使用defmacro定义宏。宏应该输出一个可以作为clojure代码演算的列表。
  2. ;;
  3. ;; 以下宏的效果和直接写(reverse "Hello World")一致。
  4. (defmacro my-first-macro []
  5. (list reverse "Hello World"))
  6. ;; 使用macroexpandmacroexpand-1查看宏的结果。
  7. ;;
  8. ;; 注意,调用需要引用。
  9. (macroexpand '(my-first-macro))
  10. ;; -> (#<core$reverse clojure.core$reverse@xxxxxxxx> "Hello World")
  11. ;; 你可以直接eval macroexpand的结果
  12. (eval (macroexpand '(my-first-macro)))
  13. ; -> (\d \l \o \r \W \space \o \l \l \e \H)
  14. ;; 不过一般使用以下形式,更简短,更像函数:
  15. (my-first-macro) ; -> (\d \l \o \r \W \space \o \l \l \e \H)
  16. ;; 创建宏的时候可以使用更简短的引用形式来创建列表
  17. (defmacro my-first-quoted-macro []
  18. '(reverse "Hello World"))
  19. (macroexpand '(my-first-quoted-macro))
  20. ;; -> (reverse "Hello World")
  21. ;; 注意reverse不再是一个函数对象,而是一个符号。
  22. ;; 宏可以传入参数。
  23. (defmacro inc2 [arg]
  24. (list + 2 arg))
  25. (inc2 2) ; -> 4
  26. ;; 不过,如果你尝试配合使用引用列表,会导致错误,
  27. ;; 因为参数也会被引用。
  28. ;; 为了避免这个问题,clojure提供了引用宏的另一种方式:`
  29. ;; 在`之内,你可以使用~获得外圈作用域的变量。
  30. (defmacro inc2-quoted [arg]
  31. `(+ 2 ~arg))
  32. (inc2-quoted 2)
  33. ;; 你可以使用通常的析构参数。用~@展开列表中的变量。
  34. (defmacro unless [arg & body]
  35. `(if (not ~arg)
  36. (do ~@body))) ; 别忘了 do!
  37. (macroexpand '(unless true (reverse "Hello World")))
  38. ;; ->
  39. ;; (if (clojure.core/not true) (do (reverse "Hello World")))
  40. ;; 当第一个参数为假时,(unless)会演算、返回主体。
  41. ;; 否则返回nil。
  42. (unless true "Hello") ; -> nil
  43. (unless false "Hello") ; -> "Hello"
  44. ;; 需要小心,宏会搞乱你的变量
  45. (defmacro define-x []
  46. '(do
  47. (def x 2)
  48. (list x)))
  49. (def x 4)
  50. (define-x) ; -> (2)
  51. (list x) ; -> (2)
  52. ;; 使用gensym来获得独有的标识符
  53. (gensym 'x) ; -> x1281 (or some such thing)
  54. (defmacro define-x-safely []
  55. (let [sym (gensym 'x)]
  56. `(do
  57. (def ~sym 2)
  58. (list ~sym))))
  59. (def x 4)
  60. (define-x-safely) ; -> (2)
  61. (list x) ; -> (4)
  62. ;; 你可以在 ` 中使用 # 为每个符号自动生成gensym
  63. (defmacro define-x-hygenically []
  64. `(do
  65. (def x# 2)
  66. (list x#)))
  67. (def x 4)
  68. (define-x-hygenically) ; -> (2)
  69. (list x) ; -> (4)
  70. ;; 通常会配合宏使用帮助函数。
  71. ;; 让我们创建一些帮助函数来支持(无聊的)算术语法:
  72. (declare inline-2-helper)
  73. (defn clean-arg [arg]
  74. (if (seq? arg)
  75. (inline-2-helper arg)
  76. arg))
  77. (defn apply-arg
  78. "Given args [x (+ y)], return (+ x y)"
  79. [val [op arg]]
  80. (list op val (clean-arg arg)))
  81. (defn inline-2-helper
  82. [[arg1 & ops-and-args]]
  83. (let [ops (partition 2 ops-and-args)]
  84. (reduce apply-arg (clean-arg arg1) ops)))
  85. ;; 在创建宏前,我们可以先测试
  86. (inline-2-helper '(a + (b - 2) - (c * 5))) ; -> (- (+ a (- b 2)) (* c 5))
  87. ; 然而,如果我们希望它在编译期执行,就需要创建宏
  88. (defmacro inline-2 [form]
  89. (inline-2-helper form)))
  90. (macroexpand '(inline-2 (1 + (3 / 2) - (1 / 2) + 1)))
  91. ; -> (+ (- (+ 1 (/ 3 2)) (/ 1 2)) 1)
  92. (inline-2 (1 + (3 / 2) - (1 / 2) + 1))
  93. ; -> 3 (事实上,结果是3N, 因为数字被转化为带/的有理分数)

扩展阅读

Clojure for the Brave and True系列的编写宏 http://www.braveclojure.com/writing-macros/

官方文档 http://clojure.org/macros

何时使用宏? http://dunsmor.com/lisp/onlisp/onlisp_12.html


还没有评论.