Clojure,宏可以做一些无法通过函数完成的事情

Tuo*_*nen 6 lisp macros functional-programming function clojure

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

据我所知,宏和函数之间的区别在于宏的参数没有被计算,而是作为数据结构和符号传递,而返回值计算(在调用宏的地方).Macro作为读者和评估者之间的代理,在评估发生之前以任意方式转换表单.在内部,他们可以使用所有语言功能,包括函数,特殊形式,文字,递归,其他宏等.

功能正好相反.在调用之前计算参数,返回值之后不返回值.但宏和函数的镜像特性让我想知道,我们不能通过引用它们的参数(表单),转换表单,在函数内部进行评估,最终返回它的值来使用函数作为宏.这在逻辑上不会产生相同的结果吗?当然这会很不方便,但从理论上讲,每个可能的宏都有相同的功能吗?

这是简单的中缀宏

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

(infix (6 + 6)) ;-> 12
Run Code Online (Sandbox Code Playgroud)

这是使用函数的相同逻辑

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

(infix-fn '(6 + 6)) ;-> 12
Run Code Online (Sandbox Code Playgroud)

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

Thu*_*ail 11

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

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

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

这是@jkinski所指出的结果:当时eval行为m已经消失.


宏可以做什么功能不可以?

是.但是如果你能用一个函数来做,你通常应该这样做.

宏是有益的

  • 延期评估;
  • 捕捉形式;
  • 重新组织语法;

功能无法做到.

延期评估

考虑(来自Halloway&Bedra的编程Clojure)

(defmacro unless [test then]
  (list 'if (list 'not test) then)))
Run Code Online (Sandbox Code Playgroud)

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

(defn safe-div [num denom]
  (unless (zero? denom) (/ num denom)))
Run Code Online (Sandbox Code Playgroud)

......这会阻止除零,返回nil:

(safe-div 10 0)
=> nil
Run Code Online (Sandbox Code Playgroud)

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

(defn unless [test then]
  (if (not test) then))
Run Code Online (Sandbox Code Playgroud)

... 然后

(safe-div 10 0)
ArithmeticException Divide by zero ...
Run Code Online (Sandbox Code Playgroud)

在忽略它之前,潜在结果被评估为then参数.unlessunless

捕获表单和重新组织语法

假设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))))
Run Code Online (Sandbox Code Playgroud)

这个

  • 选择keys(ks)和相应的表达式(vs),
  • 将后者包装为无参数fn形式,
  • 构建从前者到后者的地图,
  • 返回一个表单,该表单通过查找地图来调用返回的函数.

细节并不重要.关键是它可以做到.

当Guido van Rossum建议向Python添加一个案例陈述时,委员会拒绝了他.因此Python没有案例陈述.如果里奇不想要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)))
Run Code Online (Sandbox Code Playgroud)

这是冗长和缓慢的,它使用堆叠和丢失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)
Run Code Online (Sandbox Code Playgroud)

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


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