clojure实战——何时使用宏

clojure实战——何时使用宏

一、记住几点

  • 在C语言中,宏在编译的时候会被文本替代,纯粹的文本替换。而在clojure中,宏在编译的时候会先被求值,然后求值后得到的数据结构代替宏原来的位置。而正是这个求值功能,使clojure的宏比C语言那种纯代码替代的宏具备更强大的功能。
  • 不管是C语言的宏还是clojure的宏,它们的起作用的时期都是“编译期”。
  • 如果可以最好不要用使用宏,优先使用函数。

二、为什么尽量避免使用宏?

最重要的原因:在clojure中,函数是头等公民,而宏不是。你无法在运行时候使用宏,也无法将一个宏作为参数传递给一个函数。宏虽然可用在函数式编程中,但宏不是函数式编程。另外,宏的代码相对于函数来说更难理解。

三、什么时候使用宏?

虽然原则上尽量不使用宏,但有一些事情是宏能做而函数不能做的,此时就需要用到宏。总体来说,包括下面三种情况:

1. 必须在编译时执行某些代码时

考虑这样一个需求:

在编译代码时,需要记录编译发生的时间。

要实现这种需求,像C语言这种宏是无法做到的,只能通过修改编译器才能做到,而使用clojure的宏,可以很简单实现这样的功能。比如可以定义如下宏:

(defmacro build-time []
  (str (java.util.Date.)))

在编译的时候,这个宏会被求值,求值结果为:

"Thu Nov 23 21:40:33 CST 2017"

然后这个字符串将替代这个宏使用的位置。这就牛逼了!我们印象中编译时是不会执行代码的,而clojure在编译时,执行了str这样的函数,因此,我们可以像写一个函数一样写一个宏。

另外,需要在编译的时候执行某些比较昂贵耗时的计算时,可以考虑使用宏进行优化。

2. 需要使用不被求值的参数时

在clojure中,经常使用宏写一些方便的语法结构(语法糖),以使代码更加优雅。此时,对于传入这些语法结构的参数,应该是不被求值的。比如clojure的when

(defmacro when
  "Evaluates test. If logical true, evaluates body in an implicit do."
  {:added "1.0"}
  [test & body]
  (list 'if test (cons 'do body)))

(macroexpand-1
  '(when 1 
     2))
;; => (if 1 (do 2))

可以看到,when其实是由if实现的,其中body不应该在test检查之前被求值。在上述例子中,编译求值的时候,listcons会被执行,求值后返回一个list数据结构

3. 需要使用内联代码

有时候函数调用代价比较高,或者是通过函数调用的形式不能达到使用宏替换的效果。比如,我们经常看到的日志输出:

在输出日志时,需要知道该日志输出所在的命名空间、在第几行输出的。

在上述需求场景中,如果我们定义一个日志输出的函数:

1 (ns my-log)
2
3 (defn log-info 
4   [message]
5   (log message)

然后再其他命名空间调用该函数来输出日志,会发现所有日志都是由my-log这个命名空间的第5行输出的,不满足要求。

此时,需要将log需要被用作内联函数,使用宏就能很好地解决这个问题:

(ns log)
(defmacro log-info 
    [msg]
    `(log message)

在其他命名空间使用该宏,编译时会代码替换,执行的时候不再是调用my-log命名空间的函数了,而是在自己命名空间直接使用(log message)

四、总结

大概总结出三点我们可能会使用到宏的场景。当然,在实际程序设计和实现过程中,可以更加灵活地使用宏。但请记住一个原则:能用函数解决的问题,就不要用宏。另外,再记住一个宏的关键特性:宏只作用于编译期

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值