This example is a little contrived. The goal is to create a macro that loops over some values and programmatically generates some code.
A common pattern in Python is to initialize the properties of an object at calling time as follows:
(defclass hair [foo bar]
(defn __init__ [self]
(setv self.foo foo)
(setv self.bar bar)))
This correctly translates with hy2py
to
class hair(foo, bar):
def __init__(self):
self.foo = foo
self.bar = bar
return None
I know there are Python approaches to this problem including attr.ib and dataclasses. But as a simplified learning exercise I wanted to approach this with a macro.
This is my non-working example:
(defmacro self-set [&rest args]
(for [[name val] args]
`(setv (. self (read-str ~name)) ~val)))
(defn fur [foo bar]
(defn __init__ [self]
(self-set [["foo" foo] ["bar" bar]])))
But this doesn't expand to the original pattern. hy2py
shows:
from hy.core.language import name
from hy import HyExpression, HySymbol
import hy
def _hy_anon_var_1(hyx_XampersandXname, *args):
for [name, val] in args:
HyExpression([] + [HySymbol('setv')] + [HyExpression([] + [HySymbol
('.')] + [HySymbol('self')] + [HyExpression([] + [HySymbol(
'read-str')] + [name])])] + [val])
hy.macros.macro('self-set')(_hy_anon_var_1)
def fur(foo, bar):
def __init__(self, foo, bar):
return None
Wbat am I doing wrong?
for
forms always return None
. So, your loop is constructing the (setv ...)
forms you request and then throwing them away. Instead, try lfor
, which returns a list of results, or gfor
, which returns a generator. Note also in the below example that I've updated the syntax for newer versions of Hy, I use do
to group the generated forms together, and I've moved a ~
so that the reading happens at compile-time, as it must in order for .
to work.
(defmacro self-set [#* args]
`(do ~@(gfor
[name val] args
`(setv (. self ~(hy.read name)) ~val))))
(defclass hair []
(defn __init__ [self]
(self-set ["foo" 1] ["bar" 2])))
(setv h (hair))
(print h.bar) ; 2
We can make this a little slicker using Hyrule's defmacro-kwargs
, and using hy.models.Symbol
in place of hy.read
:
(require
hyrule [defmacro-kwargs])
(defmacro-kwargs self-set [#** kwargs]
`(do ~@(gfor
[name val] (.items kwargs)
`(setv (. self ~(hy.models.Symbol name)) ~val))))
(defclass hair []
(defn __init__ [self]
(self-set :foo 1 :bar 2)))
(setv h (hair))
(print h.bar) ; 2