循环和转换一系列形式的宏

Macros that loop and transform a sequence of forms

我正在编写宏来简化使用 matplotlib 绘图的过程。我的第一次尝试,如下,运行正常:

(defmacro insert-ax [body] `((getattr g!ax (str '~(first body))) ~@(rest body)))

(defmacro/g! plot [main &optional title [fig-kwargs {}]]
 `(do
   (import [matplotlib.pyplot :as plt] [time [ctime]])
   (setv [g!fig g!ax] (plt.subplots #**~fig-kwargs))
   (insert-ax ~main)
   (when ~title  (.set-title g!ax ~title))
   (.savefig g!fig (if ~title ~title (ctime)))))

然后下面的代码按预期工作:

 (plot (scatter xs ys) "Data"))

这(在惯用语 Python 中)等同于

fig, ax = plt.subplots()
ax.scatter(xs,ys)
ax.set_title("Data")
fig.savefig("Data")

这很好,但我希望能够传递多种形式,每个形式都用 insert-ax 进行转换,这样我就可以向 ax 添加多个图,传递其他选项等. 具体来说,这将是 do-plot 这样

(do-plot ((scatter xs ys) (scatter ys xs) "Data 2"))

等同于(又是惯用语 Python)

fig, ax = plt.subplots()
ax.scatter(xs,ys)
ax.scatter(ys,xs)
ax.set_title("Data 2")
fig.savefig("Data 2")

但以下天真的尝试不起作用:

(defmacro/g! do-plot [main &optional title [fig-kwargs {}]]
 `(do
   (import [matplotlib.pyplot :as plt] [time [ctime]])
   (setv [g!fig g!ax] (plt.subplots #**~fig-kwargs))
   (do (lfor cmd ~main (insert-ax cmd)))
   (when ~title  (.set-title g!ax ~title))
   (.savefig g!fig (if ~title ~title (ctime)))))

这个returns一个NameError: name 'scatter' is not definedNameError: name 'scatter' is not defined。但这是可以理解的:在 insert-ax 处理之前,我过早取消引用 main。所以下一个自然的尝试:

现在我得到的错误是 expanding macro do-plot NameError: name 'cmd' is not defined。这可能是由于 main 没有被取消引用以使 lfor loop/list 理解起作用。所以下一步是尝试取消引用整个循环:

(defmacro/g! do-plot [main &optional title [fig-kwargs {}]]
 `(do
   (import [matplotlib.pyplot :as plt] [time [ctime]])
   (setv [g!fig g!ax] (plt.subplots #**~fig-kwargs))
   (do ~(lfor cmd main (insert-ax cmd)))
   (when ~title  (.set-title g!ax ~title))
   (.savefig g!fig (if ~title ~title (ctime)))))

那么我的下一个错误是expanding macro do-plot AttributeError: 'HySymbol' object has no attribute 'c'。这似乎表明(因为 AttributeError 似乎与 getattr 有关)insert-ax 定义中的 ~(first body)) 被评估为 c.

最后,出于货物崇拜行为,我尝试了以下

(defmacro/g! do-plot [main &optional title [fig-kwargs {}]]
 `(do
   (import [matplotlib.pyplot :as plt] [time [ctime]])
   (setv [g!fig g!ax] (plt.subplots #**~fig-kwargs))
   (do ~@(lfor cmd main (insert-ax cmd)))
   (when ~title  (.set-title g!ax ~title))
   (.savefig g!fig (if ~title ~title (ctime)))))

(尽管认为取消引号拼接会融合我的表格)。这会默默地失败并且不会产生任何输出。然而这里hy2pyreturns同样报错expanding macro do-plot AttributeError: 'HySymbol' object has no attribute 'c'

我还能尝试什么?

宏的子程序通常写成函数比宏更好。函数使用起来更灵活(它们是 first-class 对象),造成混淆的可能性更小,并且更容易测试。以下是我如何使用 insert-ax 作为函数执行此操作:

(eval-and-compile (defn insert-ax [body]
  `((. g!ax ~(first body)) ~@(rest body))))

(defmacro/g! do-plot [main &optional title [fig-kwargs {}]]
 `(do
   (import [matplotlib.pyplot :as plt] [time [ctime]])
   (setv [g!fig g!ax] (plt.subplots #**~fig-kwargs))
   ~@(map insert-ax main)
   (when ~title  (.set-title g!ax ~title))
   (.savefig g!fig (if ~title ~title (ctime)))))

请注意,eval-and-compile(或 eval-when-compile)是确保函数在编译期间可用的必要条件,当 (do-plot …) 被展开时。我还在内部简化了 insert-ax 一点,尽管这不是必需的。

此外,您在调用 do-plot 时错放了一个括号。你想要的是:

(do-plot ((scatter xs ys) (scatter ys xs)) "Data 2")

为了完整起见,要使用原始 insert-ax 宏编写 do-plot,请将上面的 ~@(map insert-ax main) 替换为 ~@(lfor cmd main `(insert-ax ~cmd))