I am writing macros to simplify making plots with matplotlib. My first attempt, as follows, works correctly:
(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)))))
Then the following code works as expected:
(plot (scatter xs ys) "Data"))
which (in idiomatic Python) is equivalent to
fig, ax = plt.subplots()
ax.scatter(xs,ys)
ax.set_title("Data")
fig.savefig("Data")
This is great, but I'd like to be able to pass multiple forms, each to be transformed with insert-ax
so I can add multiple plots to ax
, pass other options, etc. To be specific, this would be do-plot
such that
(do-plot ((scatter xs ys) (scatter ys xs) "Data 2"))
is equivalent to (again in idiomatic Python)
fig, ax = plt.subplots()
ax.scatter(xs,ys)
ax.scatter(ys,xs)
ax.set_title("Data 2")
fig.savefig("Data 2")
But the following naïve attempts do not work:
(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)))))
This returns a NameError: name 'scatter' is not definedNameError: name 'scatter' is not defined
. But this is understandable: I'm unquoting main
too soon, before it is processed by insert-ax
. So the next natural attempt:
Now the error I get is expanding macro do-plot NameError: name 'cmd' is not defined
. Which is probably due to the fact that main
is not unquoted in order for the lfor loop/list comprehension to work. So the next step is to try to unquote the entire loop:
(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)))))
Then my next error is expanding macro do-plot AttributeError: 'HySymbol' object has no attribute 'c'
. Which seems to indicate (because AttributeError seems to relate to getattr
) that ~(first body))
in the definition of insert-ax
is being evaluated to c
.
Finally, out of a cargo-cult behavior I tried the following
(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)))))
(despite thinking that unquote-splicing would fuse my forms). This fails silently and produces no output. Here however hy2py returns the same error expanding macro do-plot AttributeError: 'HySymbol' object has no attribute 'c'
What else can I try?