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

时间:2017-05-15 07:31:58

标签: function clojure functional-programming macros lisp

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

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

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

这是简单的中缀宏

(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

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

1 个答案:

答案 0 :(得分:11)

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

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

(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 ...

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

捕获表单并重新组织语法

假设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没有案例陈述。如果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)

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

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