循环并转换一系列表格的宏

时间:2019-09-11 11:39:03

标签: hy

我正在编写宏以简化使用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)))))

这将返回一个NameError: name 'scatter' is not definedNameError: name 'scatter' is not defined。但这是可以理解的:在main处理之前,我对insert-ax的引用太早了。因此,下一个自然尝试:

现在我得到的错误是expanding macro do-plot NameError: name 'cmd' is not defined。这可能是由于没有使用main来使lfor循环/列表理解起作用的事实。因此,下一步是尝试取消引用整个循环:

(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有关),~(first body))的定义中的insert-ax正在被评估为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)))))

(尽管认为取消引号会融合我的表单)。这将静默失败,并且不产生任何输出。但是在这里hy2py返回相同的错误expanding macro do-plot AttributeError: 'HySymbol' object has no attribute 'c'

我还能尝试什么?

1 个答案:

答案 0 :(得分:2)

宏的子例程通常比宏更好地编写为函数。函数使用起来更加灵活(它们是一流的对象),产生混乱的可能性较小,并且易于测试。这是我将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")

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