原帖地址:http://java.ociweb.com/mark/clojure/article.html#Macros
作者:R. Mark Volkmann
译者:RoySong
宏(Macros)
宏被用来为语言添加新的功能结构。它们是在读取时(read-time)用来产生代码的代码。
函数总是要对它所有的参数求值,然而宏可以决定它的哪个参数被求值。这点对于实现诸如
(if condition
then-expr
else-expr
)这样的form非常重要。如果
condition
为true,那么只有
"then"表达式会被求值。如果condition
为false,那么只有"else"表达式会被求值。这代表着if
不能被实现为函数(实际上它是一个特殊form,而不是一个宏)。其他同样因为这个原因被实
现为宏的form包括and和or,因为它们需要做“短循环”("short-circuit")。
为了确定一个指定的操作是作为函数还是宏来实现,既可以在REPL中输入(doc name
),也可以
检查它的元数据。如果是个宏的话,它的元数据会包含一个:macro关键字并有一个true值。比如,
为了确定and的实现类型,在REPL中输入以下内容:
((meta (var and)) :macro) ; long way -> true
(^#'and :macro) ; short way -> true
让我们通过编写和使用宏来轻松地实现一些例子。假设我们的代码中有很多地方需要进行不同的
操作基于某个某个数字是否真正接近于0,正数或者负数。我们想避免代码重复。这必须采用宏而不
是函数来实现,因为在某个条件下应该是一条语句被求值而不是三条(正,负,0)。采用defmacro
宏来创建一个宏:
(defmacro around-zero [number negative-expr zero-expr positive-expr]
`(let [number# ~number] ; so number is only evaluated once
(cond
(< (Math/abs number#) 1e-15) ~zero-expr
(pos? number#) ~positive-expr
true ~negative-expr)))
读取器会将对around-zero宏的调用展开到对
let
特殊form的调用上去。let特殊form里面包含了一个
对cond函数的调用,cond的参数就是各项条件以及对应的返回值。在这儿采用let特殊form是为了在
第一个参数number接收的是一个表达式而非简单值得情况下提升效率。它只会对number求值一次,
然后在cond里面两次采用这个值。而系统自动生成变量声明(auto-gensym)number#是用来产生
一个独特的符号名而不会和其他符号名冲突。这样就允许了对
hygienic macros
的创建。
宏定义开头的后引号(“`”,又名语法引证syntax quote,编者注:键盘上1左边那个
键,不是单引号)避免了里面的所有内容被求值,直到引文结束。这代表着宏主体里面的内容都会按照
字面被展开,除了带波浪线的元素外(在上面的例子中,是 number
, zero-expr
,
positive-expr
和
negative-expr)。而这些在语法引证
列表中前面带波浪线的符号,都会在展开时以其对应的值来代替。
在语法引证列表中的绑定如果它的值是序列,则可以在它的前面加上~@来代替它的个体值。
下面是使用这个宏的两个例子,预期的输出都是“+”:
(around-zero 0.1 (println "-") (println "0") (println "+"))
(println (around-zero 0.1 "-" "0" "+")) ; same thing
如果需要在某处进行不止一次的求值,则采用do特殊form来包装它们。举个例子,如果number代表
温度,而我们用一个log函数来将它写入到日志文件中,那么我们会这么编写:
(around-zero 0.1
(do (log "really cold!") (println "-"))
(println "0")
(println "+"))
为了验证这个宏是否正确地展开了,我们在REPL中输入以下内容:
(macroexpand-1
'(around-zero 0.1 (println "-") (println "0") (println "+")))
输入结果如下,不过实际中没有缩进:
(clojure.core/let [number__3382__auto__ 0.1]
(clojure.core/cond
(clojure.core/< (Math/abs number__3382__auto__) 1.0E-15) (println "0")
(clojure.core/pos? number__3382__auto__) (println "+")
true (println "-")))
下面的函数采用了around-zero宏,并将返回值封装成单词:
(defn number-category [number]
(around-zero number "negative" "zero" "positive"))
下面是一些使用函数的例子:
(println (number-category -0.1)) ; -> negative
(println (number-category 0)) ; -> zero
(println (number-category 0.1)) ; -> positive
因为宏不会对其参数求值,所以未印证的函数名可以作为参数传递给宏,然后就可以构造对这些函数
的带参调用。函数定义无法做到这一点,与之替代的是传递一个匿名函数来包装对函数的调用。
下面有一个接收两个参数的宏,第一个参数是一个函数,它拥有一个参数用于接受一个弧度数值,就像
三角函数;第二个参数直接接收一个角度数值。如果在这儿采用函数定义来替代宏,我们就不得不采用
#(Math/sin %)这种形式来代替简单的
Math/sin。注意对
#号后缀的使用通过系统来生成独特的本地绑定
名,这通常是必要的来避免同其他绑定名冲突。#和~都只能在语法印证列表中使用。
(defmacro trig-y-category [fn degrees]
`(let [radians# (Math/toRadians ~degrees)
result# (~fn radians#)]
(number-category result#)))
让我们实验一下,底下的调用预期的输出是 "zero", "positive", "zero"和"negative"。
(doseq [angle (range 0 360 90)] ; 0, 90, 180 and 270
(println (trig-y-category Math/sin angle)))
宏的名字不能作为参数传递给函数。举个例子,一个宏的名称and不能传递给函数reduce。一种变通方案
是定义一个匿名函数来调用宏。举个例子,采用(fn [x y] (and x y))
或者 #(and %1 %2)这样的形式。宏
会在读取时在匿名函数内部展开。当这个匿名函数作为参数传递给其他函数比如reduce,实际上是一个函数
对象而不是宏的名字被传递。
对宏的调用是在读取时被处理的。
分享到:
相关推荐
clojure-utils, 各种小型但方便的clojure实用程序函数库 各种小型但方便的Clojure实用程序函数库特别关注:Clojure.java - 用于从Java调用Clojure的实用工具函数的Java类arrays.clj - 操作Java数组core.clj - 应该在...
clojure-1.5.1.jar
Clojure入门介绍: Clojure - Functional Programming for the JVM
该版本为稳定版,将zip文件解压,放到某个指定目录,cd进入这个目录,执行以下命令即可java -cp clojure-1.5.0.jar clojure.main。...当今最主流的运算平台JVM,把函数式编程语言引入JVM也是新方向。
clojure-cheatsheet, 用于Emacs的Clojure Cheatsheet 用于Emacs的难以置信方便的 Clojure Cheatsheet,更新为 Clojure,打包成简单,快速,可以搜索的离线形式: 状态准备使用基于 Clojure 1.7.0.安装如果你连接到 ...
Clojure是一个JVM的动态函数式语言,最近发布了1.0版本,版本中Clojure提供了一个健壮的代码。Clojure是一套发展快速的新的编程语言,特别是,它为多核计算做了新的解决方案。在今后的软件设计中无论使用Clojure语言...
clojure-csv, 从Clojure读取和写入CSV文件的库 csvclojure CSV是一个用于读取和写入CSV文件的小型库。 主要功能:接受两个常见行终止符。CSV字段内的引号和转义符是正确的句柄。解析时支持在CSV字段中嵌入未转义的行...
java运行依赖jar包
clojure 1.8.0
Clojure是一种LISP风格的语言,运行在JVM上。Clojure的一大特色就是其并发机制,它支持不可变的数据结构(Clojure是来自于可持久化的数据结构)。Clojure还有一个特色是软件事务存储(Software Transactional Memory...
clojure-sha-3-源码.rar
clojure-must-watch-源码.rar
Clojure的。 采用类似于草书的方法静态分析代码。 •••••• 总览 该项目的目标是为所有编辑人员带来适用于Clojure的出色编辑工具。 它旨在与您一起工作,以帮助您导航,识别和修复错误,执行重构等等! 你会...
Fork of http://code.google.com/p/clojure-jsr223/ that loads the Clojure runtime lazily. Copyright (c) 2009 Armando Blancas. All rights reserved. The use and distribution terms for this software are ...
clojure-style-guide:Clojure编程语言的社区编码风格指南
clojure-basics-源码.rar
Clojure入门教程- Clojure – Functional Programming for the JVM中文版
clojure-1.6 java jvm
clojure编程语言的语法基础。 如何安装 确保安装了bash,curl,rlwrap和java以下工具。 您可以通过运行以下命令来安装它们: sudo apt install curl rlwrap default-jdk 。 使用以下命令下载clojure安装脚本: ...
Scala与Clojure函数式编程 Functional.Programming.Patterns.in.Scala.and.Clojure