首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在宏上调用宏

在宏上调用宏
EN

Stack Overflow用户
提问于 2017-02-05 13:14:59
回答 2查看 613关注 0票数 3

我正在尝试编写一个Clojure宏,该宏从一个简单的infix表示法列表创建一个用于计算的前缀表示法列表,比如(2 * 3 + 4 * 2)到一个计算过的(+ (* 2 3) (*4 2))(结果是14被返回)。

我编写了以下代码:

代码语言:javascript
复制
(defmacro infix [op inlist]
  (let [[i1 i2 & i3] inlist
        last-group? (nil? (second i3))]  

    (if last-group?
      `(if (= ~op ~i2)
        (~i2 ~i1 ~(first i3)) ; return unevaluated prefix list
        (~i1 ~i2 ~(first i3))) ; return unevaluated infix list
      `(if (= ~op ~i2)
         ; recur with prefix list in i1 position
         (infix ~op ~(conj (rest i3) (list i2 i1 (first i3)) ))
         ; return as list: i1 and i2, recur i3 (probably wrong)
         (~i1 ~i2 (infix ~op ~i3))
         ))))

为了执行操作符优先级,使用不同的op (运算符函数)参数递归地调用宏:

代码语言:javascript
复制
(infix + (infix * (2 * 3 + 4 * 2)))

上面,我只是将它与两个*+一起使用,但最终我希望调用所有操作符(或者至少是为了本练习,/*+ -)。

当我执行上述嵌套的宏调用时,会得到以下错误:

代码语言:javascript
复制
CompilerException java.lang.RuntimeException: Can't take value of a macro: #'cbat.ch7.ex2/infix, compiling:(/tmp/form-init4661580047453041691.clj:1:1)

调用单个运算符的宏和相同运算符(即(infix * (2 * 3 * 4)))的列表按预期工作。如果我用单个(i1 i2 i3)列表调用宏,如果opi2不同,它将尝试(可以理解)返回带有错误的未计算的infix列表:

代码语言:javascript
复制
ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn  cbat.ch7.ex2/eval3003 (form-init4661580047453041691.clj:1)

我希望递归调用宏意味着可以在计算整行之前处理未计算的infix列表,但这似乎不起作用。

我确信后者的其他分支--内部if (即(~i1 ~i2 (infix ~op ~i3))) --是不正确的,我可能只需要内部infix调用,但我更关心的是如何获得不同运算符在评估之前工作的嵌套宏调用。

我知道这并不是将infix转换为前缀表示法的通常方法,并且已经了解到了Dijkstra调车场算法,但是请有人给我一个善意的启发:

  1. 这种嵌套的宏调用是否可能?
  2. 我的逻辑是否合理,离解决方案不远?如果是这样..。
  3. ..。我需要做什么改变才能让事情运转起来?

我真的专注于学习Clojure,所以任何彻底的解释(在可能的情况下)都将是最受欢迎的。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-02-05 20:21:53

如以下代码所示,可以嵌套宏调用:

代码语言:javascript
复制
(defmacro mac [tag & forms]
  `(do
     (println "mac - enter" ~tag)
     ~@forms
     (println "mac - exit " ~tag)))

(mac :a
  (doseq [i (range 3)]
    (mac :b (println i))))

mac - enter :a
mac - enter :b
0
mac - exit  :b
mac - enter :b
1
mac - exit  :b
mac - enter :b
2
mac - exit  :b
mac - exit  :a

还可以进行递归宏调用,如下所示:

代码语言:javascript
复制
(defmacro macr [n]
  (if (zero? n)
    1
    `(* ~n (macr ~(dec n)))))

(macr 5)   => 120

在不深入研究您的具体实施情况的情况下,我建议两点:

  1. 至少,首先,保持表单尽可能简单。这意味着只有像(2 + 3)这样的表单。特别是不要强迫宏在早期版本(或任何时候!)中计算操作符优先级。
  2. 宏几乎从来都不是必需的,不幸的是,它们在学习Clojure和其他lisps时有些“过度炒作”。我建议您在头一两年甚至不要考虑它们,因为它们比函数更脆弱,在重要方面也不那么强大(例如,您不能将宏传递到函数中)。

更新

每当你想要写一些复杂的东西(一个宏绝对有资格!),从小开始,一步一步地构建它。在这里,使用lein测试刷新插件图佩洛图书馆绝对有帮助。

首先,使最简单的宏尽可能并观察其行为:

代码语言:javascript
复制
(ns tst.clj.core
  (:use clj.core clojure.test tupelo.test)
  (:require [tupelo.core :as t] ))
(t/refer-tupelo)

(defn infix-fn [[a op b]]
  (spyx a)
  (spyx op)
  (spyx b)
  )

(defmacro infix [form]
  (infix-fn form))

(infix (2 + 3))

a => 2
op => +
b => 3

对于许多宏来说,将macros发送到像infix-fn这样的助手函数是很有帮助的。spyx通过打印符号及其价值来帮助我们。此时,我们可以简单地将args重新排序为前缀表示法,然后就可以了:

代码语言:javascript
复制
(defn infix-fn [[a op b]] (list op a b))

(defmacro infix [form] (infix-fn form))

(deftest master
  (is= 5 (infix (2 + 3)))
  (is= 6 (infix (2 * 3))))

如果我们有一个递归树结构呢?检查是否需要在infix-fn中恢复

代码语言:javascript
复制
(declare infix-fn)

(defn simplify [arg]
  (if (list? arg)
    (infix-fn arg)
    arg))

(defn infix-fn [[a op b]]
  (list op (simplify a) (simplify b)))

(is= 7 (infix ((2 * 2) + 3)))
(is= 9 (infix ((1 + 2) * 3)))


(is= 35 (infix ((2 + 3) * (3 + 4))))
(is= 26 (infix ((2 * 3) + (4 * 5))))

我不想添加复杂的运算符优先。如果绝对有必要的话,我不会自己编写代码,而是会为此使用优秀的英斯塔帕斯图书馆

票数 2
EN

Stack Overflow用户

发布于 2017-02-06 08:59:58

扩展你的电话会给你一个线索:

代码语言:javascript
复制
(if (= + *) 
  (* infix (2 * 3 + 4 * 2)) 
  (infix * (2 * 3 + 4 * 2)))

我想,你有错误的假设,认为宏的论点会在宏本身之前展开。但实际上,在这篇文章中:(~i2 ~i1 ~(first i3)) i1仍然是infix的符号。据我所见,解决方案是添加一些新的条件分支,以一些特殊的方式处理infix

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42052478

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档