IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Clojure的宏

    isnowfy发表于 2013-05-19 15:52:43
    love 0

    clojure

    Clojure是运行在java虚拟机上的一种lisp的方言。说道lisp的话最先想到的应该是函数式编程,括号之类的词语,话说大家在读了《黑客与画家》这本书后肯定都会觉得lisp很厉害,都想来试试。而clojure作为lisp的一种方言,当然是包含了lisp的各种强大特点,而lisp的很强大很灵活的一个原因要归功于他的宏。说道宏的话,c里也是有宏的概念的,而lisp的宏其实本质和c里的宏都是一样的,都是做代码替换,但是lisp的宏应用起来更加强大。

    先来感受下clojure宏的方便之处。比如有很多地方我们需要print代码和变量内容来差错,于是我们可以写这样一个宏。

    1. (defmacro dbg[x] `(let [x# ~x] (println '~x "=" x#) x#))

    当我们需要检查这段代码的时候

    1. (defn tt [x] (+ x 1))

    我们可以这样写

    1. (defn tt [x] (dbg (+ x 1)))

    当我们运行代码的时候我们能得到相应的显示结果

    1. user=> (tt 1)
    2. (+ x 1) = 2
    3. 2

    看到我们用defmacro定义了一个dbg的宏,这个宏的作用就是现实代码段和代码执行的结果,并将结果返回回去。在clojure的宏里我们主要会用到这么几个符号,首先是`表示syntax quote,'表示quote,~表示unquote,~@表示unquote splicing,详细说明下。如果某段代码前面加了'就表示这段代码被quote而不会去求值了,而`的syntax quote则表示会把相应的变量变成有namespace的形式,比如

    1. user=> 'x
    2. x
    3. user=> `x
    4. user/x

    因为默认的namespace是user,所以写clojure的宏时会和自己开头定义的ns不同,要注意变化。而~和`是搭配使用的,~必须在`的后面,并且~的数量不能超过`的数量,~是用来将变量的值替换到相应位置,比如

    1. user=> (def a 123)
    2. #'user/a
    3. user=> `(def b ~a)
    4. (def user/b 123)

    可以看到~a被替换为a的值123了,而~@的作用和~类似,不过~@不但会替换掉值并且会把括号去掉,比如

    1. ser=> (def c [1 2 3])
    2. #'user/c
    3. user=> `(def d [~@c])
    4. (def user/d [1 2 3])

    那么几个符号我们都说清楚了,再说defmacro的作用就是在代码编译的时候,会把defmacro当作是函数运行一次,并且把这个的返回结果替换到原有的位置上去,就像这样

    1. user=> (defmacro t1 [] (let [a1 (+ 1 1)] `(defn cc [] println ~a1)))
    2. #'user/t1
    3. user=> (t1)
    4. #'user/cc
    5. user=> (cc)
    6. 2

    看上去宏和函数还是很相似的,为什么需要有宏这么个东西呢,首先我们需要注意的是,传给宏的代码是不会求值的,这点和函数非常不同,函数传的参数都是先求值再去做函数运算,看下面的例子

    1. user=> (defn aa [] (println "aa") 1)
    2. #'user/aa
    3. user=> (defn bb [] (println "bb") 2)
    4. #'user/bb
    5. user=> (defn cc [c a b] (if c a b))
    6. #'user/cc
    7. user=> (defmacro dd [c a b] (if c a b))
    8. #'user/dd
    9. user=> (cc true (aa) (bb))
    10. aa
    11. bb
    12. 1
    13. user=> (dd true (aa) (bb))
    14. aa
    15. 1

    因为函数的参数是先求值的,所以调用cc的时候bb也被运行,这不是我们所希望的,我们所希望的是像dd那样只去执行aa,而不去执行bb,所以这里就需要用宏了。

    还有一个宏和函数的重要的不同是宏是在编译代码的时候运行的,运行一次之后就会把宏的返回值替换到代码的相应位置了。所以宏的话其实更像是元编程一类的东西,用代码去生成代码。

    参考资料:
    http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html
    http://orbbyrp.com/2012/06/lisp_first_step_macro.html
    http://www.cnblogs.com/me-sa/archive/2013/03/18/clojure-macro.html
    http://clojure.org/cheatsheet
    我猜您可能还会喜欢:

    • 基于用户的协同过滤和皮尔逊相关系数
    • 自动下载豆瓣FM的加心歌曲
    • 理解OAuth
    • 换用新的代码高亮插件
    • 网络涂鸦板
    • 字符编码和中文乱码小叙
    • 拼写纠正



沪ICP备19023445号-2号
友情链接