clojure实战——宏
来源:互联网 发布:seo顾问服务 编辑:程序博客网 时间:2024/05/21 06:37
clojure实战——宏
本博客主要介绍clojure中宏相关的基础知识,因为自己没能很深入研究clojure的宏,所以做不到深入的讲解。但根据自己及什么clojure用的比较好的人的经验,能不用宏就不用宏,用宏、特别是逻辑复杂的宏机会真的很少,因此我个人觉得本博客所涉及的东西已经足够应付一般的场景了。
clojure宏概述
clojure宏在编译期间被求值,而不是文本替换(和C语言的预编译不同),宏的求值过程也叫做”宏展开”;
宏求值的结果是返回一个clojure的数据结构,这个数据结构会代替宏原来的位置。
clojure的源代码会被clojure reader读入,将其以文本形式求值出一个clojure数据结构,如:(fn [a] :a 123)求值出来的数据结构是一个列表:包含一个符号,一个包含符号的vector,一个关键字和一个数字;而这个数据结构本身就是clojure语言的基本数据结构。同样,宏(reverse-it (nf [a] :a 123))也会返回一个同样的数据结构。
一门语言的代码可以用语言自身的数据结构来描述,称为”同像性”
一个简单的宏
一个翻转符号(symbol)的宏(必须将clojure的符号反着写,如prn必须写成nrp)
(defmacro reverse-it [form] (walk/postwalk #(if (symbol? %) (symbol (str/reverse (name %))) %) form))(comment (reverse-it (nrp "lz")) ;; => "lz" clojure编译期间会将其求值为:(prn "lz") (reverse-it (prn "lz")) ;; => CompilerException java.lang.RuntimeException: Unable to resolve symbol: nrp in this context )
宏的调试
clojure类库中提供了一些工具函数,用于宏的调试(查看宏扩展出来的代码),最常用的有:
clojure.core/macroexpand-1
查看宏产生的代码,扩展宏一次,如果宏里面调用其他宏,或扩展之后返回的还是宏调用,则其他宏不会被扩展。clojure.core/macroexpand
如果扩展完一次宏之后,返回的还是一个宏调用,则会再次扩展,直到顶级形式不再是个宏。注意这不是嵌套的宏!clojure.walk/macroexpand-all
彻底扩展一个宏,包括所有的嵌套宏。但它对一些特殊情况处理不完全正确,不赘述,一般用不到。
示例:
(comment (macroexpand-1 '(reverse-it (nrp "lz"))) ;; => (prn "lz") (macroexpand '(reverse-it (nrp "lz"))) (walk/macroexpand-all '(reverse-it (nrp "lz"))))
宏安全
宏是在编译期执行的,而在编译期间,宏并不知道某个符号是不是已经被定义。它看到的就是列表、符号以及其他数据结构。它返回的也是列表、符号和其他数据结构。所以在宏里面用了一些外部没有定义的符号,编译时也不会出错,但是执行的时候就会出问题。
示例:
(defmacro unknow-symbol [form] `(str "unknow symbol " a ~form));; a没有定义时,执行出错:(unknow-symbol "bbb");; => CompilerException java.lang.RuntimeException: No such var: clojure-study.micro/a,;; a定义时,执行正常(def a "a")(unknow-symbol "bbb");; => "unknow symbol abbb"
宏的主要风险是:宏产生的代码与外部代码发生不正常的交互!
宏无法访问运行时的值,不能作为值进行组合或者传递。
对于那些对于需要传递高阶函数的地方,避免用宏。
如:
(comment (map reverse-it '((+ 1 3) (+ 3 4))) ;; => CompilerException java.lang.RuntimeException: Can't take value of a macro: #'clojure-async.micro-symbols/reverse-it, ;; 编译时出错,宏不能作为一个值传递给map, ;; 虽然map的第一个参数是一个函数fn,但是clojure中函数也是数据,也是一个值。 (map #(reverse-it %) [(+ 1 3) (+ 3 4)]) ;; => NullPointerException ;; 运行时错误)
宏的另一个危险的地方是:当红内部要绑定一个本地符号时,这个符号可能会和外部代码的冲突,这样宏一旦扩展出来,极有可能就发生异常,而这种异常通过查阅代码是很难发觉的。
为了避免上述情况,可以在宏内绑定一个本地绑定时,符号以#结尾。
在语法引述形式中任何以#结尾的符号都会被自动扩展,并且对于前缀相同的符号,会被扩展为同一个符号的名字。这样可以避免宏里面的符号与外部代码的相冲突。
如:
(comment `(x# x#) ;; => (x__2284__auto__ x__2284__auto__) (defmacro println-mcro [y] (let [y# "macro"] `(println ~y# ~y))) (println-mcro 1) ;; => macro 1 )
小结
- 应该尽量少用宏,只有在函数满足不了的情况下,才用宏。
- 即使要用宏,应该只是用它做一些简单的组织工作,真正的逻辑都要放在真正的函数中。
- 宏的使用场景:
- 需要特殊的求职语义;
- 需要自定义语法——特别是一些领域特定表示法。
- 需要在编译器提前计算一些中间值。
- 用之前,始终问问自己,用函数不能解决吗?!
- clojure实战——宏
- clojure实战——schema for clojure
- clojure实战——何时使用宏
- clojure实战——配置文件
- clojure实战——符号&@#'+-*/
- clojure实战—— :pos & :pre断言
- clojure实战——midje测试框架
- clojure实战——日志处理
- clojure实战——函数内存化
- clojure实战——引述相关'`~~@
- clojure实战——binding vs let
- clojure实战——如何在java中调用clojure函数
- clojure实战——快速搭建web前端开发框架
- clojure实战--schema for clojure
- Programming Clojure学习笔记——宏
- Programming Clojure学习笔记——宏
- Programming Clojure学习笔记——宏
- Programming Clojure学习笔记——宏
- 编解码框架
- C语言程序设计习题 1-9 编写一个将输入复制到输出的程序,并将其中连续多个空格用一个空格代替
- linux简单配置phpmyadmin
- 一个JDBC简单的查询步骤
- PHP的运行机制与原理(底层)
- clojure实战——宏
- python模拟登陆+获取数据
- java基础之IO流--ObjectOutputStream(专门用于操作对象)
- 1823:薪水计算
- SQL中间表使用
- CodeForces 6C Alice, Bob and Chocolate
- ANDROID 实现布局动态加载
- curl 的post请求模拟登陆
- 【南阳理工】 46 最少乘法次数