-
Notifications
You must be signed in to change notification settings - Fork 371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Macro name shadowing/collisions and removal #1689
Comments
One thing to keep in mind is that in at least one way, we're currently taking advantage of this: It wouldn't be too unreasonable to forbid users from giving a macro the same name as a special operator. That could be part of a solution, perhaps. |
In Clojure, if I recall, a special form in the function position always takes precedence over a macro with the same name. To use a macro with the same name as a special form, you'd have to use its fully qualified name; the unqualified name is assumed to mean the special form. |
Out of curiosity, I made some minor edits that expose the macro functions at module level. hy 0.15.0+43.g31a630d using CPython(default) 3.6.6 on Linux
=> (defmacro test [] (print "macro"))
=> (test)
macro
=> test
<function _hy_anon_var_1 at 0x7f0be5b2cea0>
=> (defn test [] (print "function"))
=> (test)
function
=> test
<function test at 0x7f0be5b2c950>
=> (del test)
=> test
Traceback (most recent call last):
File "/home/bwillard/projects/code/python/hy/hy/importer.py", line 173, in hy_eval
return eval(ast_compile(expr, "<eval>", "eval"), globals, locals)
File "<eval>", line 1, in <module>
NameError: name 'test' is not defined
=> (test)
Traceback (most recent call last):
File "/home/bwillard/projects/code/python/hy/hy/importer.py", line 173, in hy_eval
return eval(ast_compile(expr, "<eval>", "eval"), globals, locals)
File "<eval>", line 1, in <module>
NameError: name 'test' is not defined
=> The changes are in an experimental Some interesting things have already come up, and I'll describe them here when I get a minute. |
I think that @brandonwillard's approach is the correct way forward, but it doesn't fully solve the problem. When running a script, macros still have to get expanded out before any of the code is actually evaluated. Hence they will still shadow any functions or objects which are being called. This seems to work in a repl because the effective unit of compilation is a form, not an entire module. Hence the compiler can check the runtime environment to see if the macro has been overwritten. It seems to me that the way to actually solve this issue is to change hy's unit of compilation to a top-level form everywhere so that the compiler can interact with the runtime environment. I know that this would be a big change to how the compiler works, but the only downside in terms of backwards compatibility that I can see is that |
This might not be an issue anymore with how macros are handled in the current Hy: ;; test.hy
(defn test [] (print "fn"))
(defmacro test [] (print "macro-c") '(print "macro-e"))
(test)
(eval-when-compile
(del (get __macros__ "test")))
(test)
|
I know that macros can technically be deleted, but you can still end up in very confusing situation if you don't realise that a Python object is being shadowed by a macro. In the previous example, I don't think that |
If, for example, hy had a |
Ah ok, I see what you're saying now. |
I think that it's a step in the right direction, but it doesn't solve the issue if all macros get expanded out before any code is evaluated, because then you have no way of knowing whether a macro will be shadowed by another python object. I don't see any way of solving this without compiling form by form, but that would have other issues like breaking Maybe the best thing to do would just be to have a compile time check to see if a symbol that is already assigned to a macro is being redefined and just raise a warning to the user. |
was this fixed in #2104? A user defined python-venv-3.10.0b1 ❯ hy --spy
import hy
hy 1.0a1+159.gb47a1a74.dirty using CPython(default) 3.10.0b1 on Linux
=> (require [hy.contrib.walk [let]])
hy.macros.require('hy.contrib.walk', None, assignments=[['let', 'let']], prefix='')
None
=> (defmacro require [] 1)
hy.macros.macro('require')(lambda hyx_XampersandXcompiler: 1)
None
=> (require)
1
1
=> (hy.macros.require "hy.core.result_macros" None :assignments [["require" "require"]])
hy.macros.require('hy.core.result_macros', None, assignments=[['require', 'require']])
True
=> (require [hy.contrib.walk [let]])
hy.macros.require('hy.contrib.walk', None, assignments=[['let', 'let']], prefix='')
None granted the interface could be better, but the issue of user defined macros shadowing special forms seems to be fixed? |
Yeah, it's not obvious to the user how to do it, but it can be done now. |
so is this a |
If we want to advertise this to users, we should probably provide a nicer interface, yeah. |
probably can just make the
|
#2242 made Hy's unit of compilation a top level form and #1979 made it so builtin macros aren't auto injected into every module's Clojure 1.10.2
user=> (defmacro require [])
WARNING: require already refers to: #'clojure.core/require in namespace: user, being replaced by: #'user/require
#'user/require and then maybe add a => (require hyrule *)
=> (defmacro require [])
<string>:1: UserWarning: require already refers to: `require` in module: `builtins`, being replaced by: `some-module.require`
=> (delmacro require)
=> (require hyrule *) |
A user defined
require
macro will block the compilation of allrequire
statements and there's little recourse for undoing this. As far as I can tell, a user would have to remove the macro entry inhy.macros._hy_macros
(or__macros__
under #1682) and, since the macro dictionary is a Hy internal, that isn't a very reasonable recommendation.Nearly the same situation occurs with plain functions:
Since we can't simply
(del test)
(the macro or the plain function) or redefine the function in order to regain it, this situation can easily lead to confusion and frustration.(I imagine this might have an issue of its own already, but I didn't see one in my first pass.)
One can reset the Hy/Python session, but, when that's the easiest/best option, something needs to be fixed!
This general discord between macros, plain functions and Hy special forms like
require
is not a trivial issue, and, while we could make it easier to work around the aforementioned situations (e.g. have the compiler applydel
to macros and tags), we should simply rethink and/or formalize the interaction between these three.For instance, I believe I've mentioned/asked this before, but if the functions backing macros used their macro's names and were defined directly in their namespaces, then this wouldn't be an issue.
Anyway, thoughts?
The text was updated successfully, but these errors were encountered: