原帖地址: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,实际上是一个函数
对象而不是宏的名字被传递。
对宏的调用是在读取时被处理的。
分享到:
相关推荐
史上最全编程语言全套教程,共99门...函数式编程语言 壳编程语言 常见编程语言 并行编程语言 数据分析编程语言 数据库查询语言 系统编程语言 脚本编程语言 逻辑编程语言 面向对象编程语言 等所有常见的变成语言系列教程
- **Clojure**作为一种功能强大且灵活的函数式编程语言,在JVM上运行具有独特的优势。它不仅支持高效的函数式编程,还具备出色的并发编程能力,使其成为开发高性能、高并发系统的理想选择。 - 对于希望深入了解函数...
Clojure是一种运行在Java虚拟机(JVM)上的动态函数式编程语言,它属于Lisp家族。Clojure的设计哲学强调了函数作为一等公民的地位,以及数据的不可变性,这些特性使得Clojure在处理并发编程和数据密集型应用时表现...
clojure-utils, 各种小型但方便的clojure实用程序函数库 各种小型但方便的Clojure实用程序函数库特别关注:Clojure.java - 用于从Java调用Clojure的实用工具函数的Java类arrays.clj - 操作Java数组core.clj - 应该在...
1. **Clojure编程语言**:Clojure是一种基于Lisp的函数式编程语言,运行在Java虚拟机(JVM)上。它强调 immutability(不可变性)、concurrency(并发性)和functional programming(函数式编程)特性。 2. **函数...
Scala和Clojure都是现代函数式编程语言,它们都运行在Java虚拟机(JVM)上,能够利用Java的生态系统和资源。Scala是一种多范式语言,结合了面向对象和函数式编程的特点,提供了强大的类型系统和模式匹配功能。...
clojure-1.5.1.jar
Clojure是一种基于Lisp的现代函数式编程语言,它运行在Java虚拟机(JVM)上,充分利用了Java平台的强大功能。SHA-3,全称为Secure Hash Algorithm 3,是美国国家标准与技术研究所(NIST)发布的一种密码散列函数标准...
Lisp是一种古老的函数式编程语言,其主要特点是使用S-表达式(Symbolic Expression)表示数据和程序。Lisp的数据结构核心是列表,而列表是通过递归定义的,这使得在Lisp中实现递归算法变得十分自然。它的宏系统允许...
5. Clojure:基于JVM的动态类型语言,支持函数式编程和基于宏的 metabrogramming。 Java平台的多语言混合编程的未来发展趋势是: 1. 软件项目的未来在于混合语言编程,Java仍将是JVM生态系统中的重要组成部分。 2....
Clojure是一种基于Lisp的函数式编程语言,它运行在Java虚拟机(JVM)上,充分利用了Java的生态系统。这个“clojure-basics-源码.rar”压缩包很可能是包含了一些基本Clojure编程概念的示例代码或者教程。虽然没有具体...
Clojure是一种功能强大的Lisp方言,它在Java虚拟机(JVM)上运行,并且具有丰富的函数式编程特性。这个压缩包"clojure-must-watch-源码.rar"似乎包含了与Clojure相关的源代码,可能是为了帮助学习者深入理解Clojure...
虽然Java本身不是一种函数式编程语言,但是JVM强大的生态系统为运行函数式编程语言提供了坚实的基础。 本书的作者Michael Bevilacqua-Linn通过自己的经验,结合了多个编程实践者的点评,将关于Scala和Clojure中函数...
Clojure是一种基于Lisp的函数式编程语言,它运行在Java虚拟机(JVM)上,充分利用了Java生态系统的优势。这个压缩包很可能是Clojure 1.6.0-RC3的源代码发布,开发者可以下载、编译和测试以确保其稳定性和兼容性。 ...
8. **函数式编程语言**:一些语言天生就是函数式的,如Haskell、Lisp和Clojure;还有一些语言虽然不是纯函数式的,但支持函数式编程特性,如Python、JavaScript和Scala。 9. **monads(范畴论)**:在高级函数式...
Clojure入门介绍: Clojure - Functional Programming for the JVM
Clojure是一种基于Lisp的函数式编程语言,它运行在Java虚拟机(JVM)上,充分利用了Java的生态系统。Clojure的设计目标是提供一种静态类型的、并发的、内存安全的语言,同时保持Lisp的简洁性和灵活性。在这个压缩包...
该版本为稳定版,将zip文件解压,放到某个指定目录,cd进入这个目录,执行以下命令即可java -cp clojure-1.5.0.jar clojure.main。...当今最主流的运算平台JVM,把函数式编程语言引入JVM也是新方向。
史上最全编程语言全套教程,共99门...函数式编程语言 壳编程语言 常见编程语言 并行编程语言 数据分析编程语言 数据库查询语言 系统编程语言 脚本编程语言 逻辑编程语言 面向对象编程语言 等所有常见的变成语言系列教程
- **Lisp**及其衍生语言(如Scheme和Clojure):历史悠久的函数式编程语言,支持宏定义等高级特性。 - **Scala**:结合了面向对象和函数式编程特性的多范式语言。 - **Erlang**:强调并发处理和分布式系统的函数式...