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实战——宏相关推荐

  1. Clojure 学习入门(11)- 宏 macro

    clojure macro宏在运行之前机械展开,定义宏相当于给语言增加新特性,写宏的*原则*:  能写成函数就不要用宏(因为写宏没有写函数简单直观,容易写错,需要先在 REPL 中测试一番) 只有不得 ...

  2. clojure java.jdbc_Clojure驱动的Web开发

    Clojure是运行在JVM之上的Lisp方言,提供了强大的函数式编程的支持.由于Java语言进化的缓慢,用Java编写大型应用程序时,代码往往十分臃肿,许多语言如Groovy.Scala等都把自身设 ...

  3. 《程序员2013精华本》

    <程序员2013精华本> 基本信息 作者: <程序员>杂志社 出版社:电子工业出版社 ISBN:9787121224560 上架时间:2014-2-21 出版日期:2014 年 ...

  4. 当OpenCV遇上Origami

    导语 作为计算机视觉库,OpenCV一直被广泛应用于各种图像和视频相关的项目中,更在推动人工智能和神经网络的发展中发挥了重要作用.但是学习和使用OpenCV并不简单,总是需要些时间选择合适的类库.合适 ...

  5. 《程序员》2013年1期精彩内容:产品设计

    <程序员>封面报道:产品设计 每家企业.每个设计师都希望自己能够像乔布斯那样做出伟大的产品,中国设计在逐渐走向成熟,虽然面对的挑战还很大.设计师.企业.咨询以及其他众多协作团队构成影响产品 ...

  6. PICRUSt2分析实战:16S扩增子OTU或ASV预测宏基因组、新增KEGG层级

    PICRUSt2分析实战:16S扩增子OTU或ASV预测宏基因组.新增KEGG层级 更新时间:2021年7月8日 PICRUSt推出了近8年,引用5000余次. 现推出PICRUSt2,202年再次霸 ...

  7. PICRUSt2分析实战:16S扩增子OTU或ASV预测宏基因组EC、通路、KO(200806更新)

    PICRUSt2分析实战:16S扩增子OTU或ASV预测宏基因组 更新时间:2020年8月6日 PICRUSt推出了近7年,引用4000余次. 现推出PICRUSt2,再次霸气发表于顶级期刊Natur ...

  8. 宏基因组实战4. 基因注释Prokka

    前情提要 如果您在学习本教程中存在困难,可能因为缺少背景知识,建议先阅读本系统前期文章 宏基因组分析理论教程 微生物组入门圣经+宏基因组分析实操课程 1背景知识-Shell入门与本地blast实战 2 ...

  9. Microbiome:宏基因组分箱流程MetaWRAP分析实战和结果解读

    文章目录 MetaWRAP-a flexible pipeline for genome-resolved metagenomic data analysis 分析实战 0.下载肠道宏基因组数据 1. ...

最新文章

  1. HDFS Java 客户端使用(Windows开发环境)
  2. SpringBoot--HelloWord
  3. halcon/c++接口基础 之 HALCON图像变量类
  4. Struts2的Action配置的各项默认值
  5. 干货|一文看懂BLE低功耗技术-附主流BLE芯片厂商介绍
  6. java模拟器配置_JAVA模拟器全功略!
  7. Spring入门之bean的配置
  8. OSChina 周四乱弹 —— 开个程序门诊?
  9. php写phalapi,PhalApi框架
  10. win10 永久关闭自动更新
  11. [分形学] Julia Set (茱莉亚集) VC 源代码
  12. 快递/短信等热门API大全分享
  13. mysql grant失败_grant授权“失败”的原因
  14. 如何取消shutdown关机命令?-shutdown命令的使用解析
  15. 2016计算机论文参考文献,2016大学毕业设计计算机软件论文摘要和结论参考文献俱全.doc...
  16. 家轿进化史|谁说家用与运动,不可兼得?
  17. Android Java代码中获取App渠道信息
  18. MATLAB学习笔记:行列式及其应用
  19. JS检测是否有企业微信应用程序
  20. R语言使用ifelse函数进行变量编码(recode):把dataframe中连续变量基于条件表达式转化为两个类别的离散变量(分类变量)

热门文章

  1. STM32单片机电源端并联电容的重要性
  2. 2021-10期自考总结
  3. iOS icon 尺寸
  4. 学习型索引在数据库中的应用实践
  5. 9Django-----------Django框架------------安装使用、基本介绍
  6. linux用c实现pwd命令,linux pwd指令的C实现
  7. 菜鸟炒美股(一) (转)
  8. 洛谷——P3906 Geodetic集合
  9. Python 利用聚类算法对图片进行颜色压缩
  10. 如何申请 @msn.com 邮箱