modulemacroshy

Hy invisible module function in quasiquoted macro


I have a Hy module that looks something like this:

; a.hy

(defn _add [x y]
  (+ x y))

(defmacro m [x]
  `(_add ~x 2))

I export part of the macro's functionality as a regular function in the module file, which is good practice as mentioned in the Hy manual. Because of the quasiquote, this does not need eval-when-compile for the macro to work when using it in the same file. However, I would like to export the macro while hiding the function from modules that import it; this does not work as is:

; b.hy

(require a *)

(print (m 5)) ; => NameError: name '_add' is not defined

Neither eval-and-compile nor eval-when-compile fix this, and importing the module in addition to requiring it doesn't work either!

; b.hy

(require a *)
(import a *)

(print (m 5)) ; => NameError, still!

The only way I can make it work is making the macro import its own module to get access to _add:

; a.hy

(defn _add [x y]
  (+ x y))

(defmacro m [x]
  `(do (import a [_add])
     (_add ~x 2)))

This is jank, probably inconsistent (I don't know how the self-import will behave depending on project path, though right now I just have a.hy in the global path) and does not hide the helper function from importing modules:

; b.hy

(require a *)

(print (m 5))      ; => 7
(print (_add 5 2)) ; => 7 (expected NameError)

Is there a better way to do this?


Solution

  • The only way I can make it work is making the macro import its own module to get access to _add

    What you've described this way is, in fact, the right approach. Just note the difference between including an import in the expansion and importing in the macro code itself. This is the difference between (defmacro m [] `(do (import foo) …)) and (defmacro m [] (import foo) …).

    To avoid namespace pollution, see this passage in the section you linked:

    You could use import or require to bind the module name or one of its members to a gensym, but an often more convenient option is to use the one-shot import syntax hy.I or the one-shot require syntax hy.R:

    (defmacro hypotenuse [a b]
     `(hy.I.math.sqrt (+ (** ~a 2) (** ~b 2))))
    (hypotenuse 3 4)
    

    So, you'd write

    (defmacro m [x]
      `(hy.I.a._add ~x 2))
    

    Notice that you could also have _add be a function that you call to generate code, and which you then call in m itself instead of putting a call to it in the expansion:

    ; a.hy
    
    (eval-and-compile (defn _add [x y]
      `(+ ~x ~y)))
    
    (defmacro m [x]
      (_add x 2))