【问题标题】:Clojure, can macros do something that couldn't be done with a functionClojure,宏可以做一些函数不能做的事情
【发布时间】:2017-10-13 21:33:36
【问题描述】:

我正在学习 Clojure 宏,想知道为什么我们不能只使用函数进行元编程。

据我所知,宏和函数之间的区别在于宏的参数不被评估,而是作为数据结构和符号传递,而返回值 被评估(在那个地方宏被调用的地方)。宏充当读取器和评估器之间的代理,在评估发生之前以任意方式转换表单。在内部,它们可以使用所有语言特性,包括函数、特殊形式、文字、递归、其他宏等。

功能正好相反。参数在调用之前进行评估,返回值不在返回之后。但是宏和函数的镜像特性让我想知道,我们是否也可以通过引用它们的参数(形式)、转换形式、在函数内部对其进行评估、最后返回来使用函数 as 宏这是价值。这在逻辑上不会产生相同的结果吗?当然这会很不方便,但理论上,每个可能的宏都有等效的功能

这是简单的中缀宏

(defmacro infix
  "translate infix notation to clojure form"
  [form]
  (list (second form) (first form) (last form)))

(infix (6 + 6)) ;-> 12

这是使用函数的相同逻辑

(defn infix-fn
  "infix using a function"
  [form]
  ((eval (second form)) (eval (first form)) (eval (last form))))

(infix-fn '(6 + 6)) ;-> 12

现在,这种看法是否适用于所有情况,或者是否存在一些宏观无法超越的极端情况?最后,宏只是函数调用的语法糖吗?

【问题讨论】:

  • 请注意,宏在宏扩展时(通常在编译之前)而不是运行时递归扩展。编译后的代码就像您手动编写了展开式一样,因此没有性能损失。还要记住eval 在空词法环境中评估表单。 (let [x 10] (infix-fn '(x + 6))) => CompilerException ... Unable to resolve symbol: x
  • 这些都是我没想到的好点

标签: function clojure functional-programming macros lisp


【解决方案1】:

如果我在回答之前阅读问题会有所帮助。

你的中缀函数除了文字之外不起作用:

(let [m 3, n 22] (infix-fn '(m + n)))
CompilerException java.lang.RuntimeException: 
Unable to resolve symbol: m in this context ...

这是@jkinski 指出的结果:当eval 行动时,m 已经消失了。


宏可以做哪些功能不能做的事情?

是的。但是如果你可以用一个函数来做到这一点,你通常应该这样做。

宏有利于

  • 延迟评估;
  • 捕获表单;
  • 重新组织语法;

没有一个函数可以做的事情。

延迟评估

考虑(来自 Halloway 和 Bedra 的 Programming Clojure

(defmacro unless [test then]
  (list 'if (list 'not test) then)))

...if-not 的部分克隆。让我们用它来定义

(defn safe-div [num denom]
  (unless (zero? denom) (/ num denom)))

...防止被零除,返回nil

(safe-div 10 0)
=> nil

如果我们尝试将其定义为函数:

(defn unless [test then]
  (if (not test) then))

...然后

(safe-div 10 0)
ArithmeticException Divide by zero ...

unless 的主体忽略它之前,潜在结果被评估为unlessthen 参数。

捕获表单并重新组织语法

假设 Clojure 没有 case 表单。这是一个粗略的替代品:

(defmacro my-case [expr & stuff]
  (let [thunk (fn [form] `(fn [] ~form))
        pairs (partition 2 stuff)
        default (if (-> stuff count odd?)
                  (-> stuff last thunk)
                  '(constantly nil))
        [ks vs] (apply map list pairs)
        the-map (zipmap ks (map thunk vs))]
    (list (list the-map expr default))))

这个

  • 将键 (ks) 和对应的表达式 (vs) 分开,
  • 将后者包装为无参数fn 形式,
  • 构建从前者到后者的映射,
  • 返回一个表单,该表单调用通过查找返回的函数 地图。

细节不重要。关键是可以做到。

当 Guido van Rossum 提议在 Python 中添加一个案例语句时,委员会拒绝了他。所以 Python 没有 case 语句。如果 Rich 不想要 case 声明,但我想要,我可以拥有一个。


只是为了好玩,让我们使用宏来设计一个可以通过的if 形式的克隆。这无疑是函数式编程界的陈词滥调,但却让我感到意外。我曾认为if 是惰性求值的不可简化的原语。

一个简单的方法是搭载my-case 宏:

(defmacro if-like
  ([test then] `(if-like ~test ~then nil))
  ([test then else]
   `(my-case ~test
     false ~else
     nil ~else
     ~then)))

这是冗长而缓慢的,它使用堆栈并丢失recur,它被埋在闭包中。不过……

(defn fact [n]
  (if-like (pos? n)
    (* (fact (dec n)) n)
    1))

(map fact (range 10))
=> (1 1 2 6 24 120 720 5040 40320 362880)

...它或多或少都有效。


亲爱的读者,请指出我的代码中的任何错误。

【讨论】:

  • 示例中的惰性求值也可以通过将 then 作为 lambda 表达式传递给 unless 函数来完成。有没有连 lambda 都做不到的情况?
  • @TuomasToivonen 我认为 lambdas 总能​​做到。如您所知,函数不能推迟的是对其参数的评估。您当然可以始终将参数作为在 lambda 中捕获的未评估形式提供。我们甚至可以使用 lambdas 创建一个类似于宏的if:看,ma,没有特殊形式。事实上,我现在就这样做:)。
猜你喜欢
  • 1970-01-01
  • 2019-09-11
  • 2019-11-08
  • 1970-01-01
  • 1970-01-01
  • 2017-03-05
  • 2011-04-12
  • 2011-02-02
  • 2010-10-28
相关资源
最近更新 更多