0

With the following (simplified) code:

(setv agnostic-manager-installers {})

(defmacro alias-assign [am &rest aliases]
    (for [alias aliases] (assoc
        agnostic-manager-installers
        (str alias)
        (-> (globals) (get (str am)) (get "install")))))

(setv brew {
    "prefix" "/home/linuxbrew/.linuxbrew/"
    "install" (defn brew [] (run
                "curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | sudo bash"
                :shell True))
})

(alias-assign brew brew homebrew home-brew linuxbrew linux-brew)

I'm getting the following error:

Traceback (most recent call last):
  File "/home/shadowrylander/.local/syvl/python/hy/bin/hy", line 8, in <module>
    sys.exit(hy_main())
  File "/usr/lib/python3.9/contextlib.py", line 137, in __exit__
    self.gen.throw(typ, value, traceback)
  File "<stdin>", line 9, in alias_assign
hy.errors.HyMacroExpansionError: 
  File "<stdin>", line 20
    (alias-assign brew brew homebrew home-brew linuxbrew linux-brew)
    ^--------------------------------------------------------------^
expanding macro alias-assign
  KeyError: 'brew'

I thought the macro was not supposed to evaluate the arguments until compile-time, if I'm reading the error correctly (which I don't think I am)? Basically, I would like to not write the double quotes around every single alias provided to alias-assign, which is why I went with a macro.

ShadowRylander
  • 361
  • 2
  • 8

1 Answers1

1

Here's some much simpler code that produces the same error:

(setv brew 1)

(defmacro m []
  (get (globals) "brew"))

(m)

Perhaps the problem is more obvious now: trying to access the global variable brew during macro-expansion doesn't work because brew doesn't exist during compile-time, when macro-expansion happens. The same problem occurs, with NameError instead of KeyError, if you just say brew instead of (get (globals) "brew"). In any case, the form (setv brew 1) won't be evaluated until run-time. One way around this is to say (eval-when-compile (setv brew 1)) instead. This makes the evaluation happen earlier.

A broader problem is that you seem to be executing code that you actually want to return, as the result of the macro expansion. After all, the body of your macro is a for, so it will always return None. Contrast with the following code, which uses quoting and unquoting to generate forms and return them (and uses updated syntax):

(setv agnostic-manager-installers {})

(defmacro alias-assign [am #* aliases]
  `(do ~@(gfor
    alias aliases
    `(setv (get agnostic-manager-installers ~(str alias))
      (get ~am "install")))))

(setv brew (dict
  :prefix "/home/linuxbrew/.linuxbrew/"
  :install "placeholder"))

(alias-assign brew brew homebrew home-brew linuxbrew linux-brew)

(print (hy.repr agnostic-manager-installers))

The result is:

{"brew" "placeholder"  "homebrew" "placeholder"  "home-brew" "placeholder"  "linuxbrew" "placeholder"  "linux-brew" "placeholder"}
Kodiologist
  • 2,984
  • 18
  • 33
  • 1. What is the difference between using `assoc` versus using `setv` to assign the install function to the dictionary in the macro? 2. I'm not actually trying to return anything in the macro; should I be? 3. Why isn't `&rest` being used in the new macro definition? – ShadowRylander Dec 27 '21 at 22:45
  • 1
    @ShadowRylander 1. Nothing. 2. Yes. If this surprises you, you should probably read a tutorial or two on macros (any Lisp will do for learning the basics). 3. `&rest` has been `#*` since 1.0a1. That's what I meant by "updated syntax". – Kodiologist Dec 28 '21 at 13:34
  • Got it; thanks again, and will do! – ShadowRylander Dec 28 '21 at 19:26