Semantics

This chapter describes features of Hy semantics that differ from Python's and aren't better categorized elsewhere, such as in the chapter Macros. Each is a potential trap for the unwary.

Implicit names

Every compilation unit (basically, module) implicitly begins with (import hy). You can see it in the output of hy2py. The purpose of this is to ensure Hy can retrieve any names it needs to compile your code. For example, the code (print '(+ 1 1)) requires constructing a hy.models.Expression. Thus you should be wary of assigning to the name hy, even locally, because then the wrong thing can happen if the generated code tries to access hy expecting to get the module. As a bonus, you can say things like (print (hy.repr #(1 2))) without explicitly importing hy first.

If you restrict yourself to a subset of Hy, it's possible to write a Hy program, translate it to Python with hy2py, remove the import hy, and get a working Python program that doesn't depend on Hy itself. Unfortunately, Python is too dynamic for the Hy compiler to be able to tell in advance when this will work, which is why the automatic import is unconditional.

Hy needs to create temporary variables to accomplish some of its tricks. For example, in order to represent (print (with …)) in Python, the result of the with form needs to be set to a temporary variable. These names begin with _hy_, so it's wise to avoid this prefix in your own variable names. Such temporary variables are scoped in the same way surrounding ordinary variables are, and they aren't explicitly cleaned up, so theoretically, they can waste memory and lead to object.__del__() being called later than you expect. When in doubt, check the hy2py output.

Order of evaluation

Like many programming languages, but unlike Python, Hy doesn't guarantee in all cases the order in which function arguments are evaluated. More generally, the evaluation order of the child models of a hy.models.Sequence is unspecified. For example, (f (g) (h)) might evaluate (part of) (h) before (g), particularly if f is a function whereas h is a macro that produces Python-level statements. So if you need to be sure that g is called first, call it before f.

When bytecode is regenerated

The first time Hy is asked to execute a file, whether directly or indirectly (as in the case of an import), it will produce a bytecode file (unless PYTHONDONTWRITEBYTECODE is set). Subsequently, if the source file hasn't changed, Hy will load the bytecode instead of recompiling the source. Python also makes bytecode files, but the difference between recompilation and loading bytecode is more consequential in Hy because of how Hy lets you run and generate code at compile-time with things like macros, reader macros, and eval-and-compile. You may be surprised by behavior like the following:

$ echo '(defmacro m [] 1)' >a.hy
$ echo '(require a) (print (a.m))' >b.hy
$ hy b.hy
1
$ echo '(defmacro m [] 2)' >a.hy
$ hy b.hy
1

Why didn't the second run of b.hy print 2? Because b.hy was unchanged, so it didn't get recompiled, so its bytecode still had the old expansion of the macro m.

Traceback positioning

When an exception results in a traceback, Python uses line and column numbers associated with AST nodes to point to the source code associated with the exception:

Traceback (most recent call last):
  File "cinco.py", line 4, in <module>
    find()
  File "cinco.py", line 2, in find
    print(chippy)
          ^^^^^^
NameError: name 'chippy' is not defined

This position information is stored as attributes of the AST nodes. Hy tries to set these attributes appropriately so that it can also produce correctly targeted tracebacks, but there are cases where it can't, such as when evaluating code that was built at runtime out of explicit calls to model constructors. Python still requires line and column numbers, so Hy sets these to 1 as a fallback; consequently, tracebacks can point to the beginning of a file even though the relevant code isn't there.