-
Notifications
You must be signed in to change notification settings - Fork 31
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
User-macro calls given as user macro arguments always compile to function calls #27
Comments
Interesting. I'm sure I'm missing something here, but why would the user want the inverse behavior?
If the user has defined/imported the |
Apologies for the excess conciseness. 😄 An example might help: (macro hello
(lambda () (return 'hi)))
(macro passthrough
(lambda (arg) (return arg)))
(passthrough (hello)) At the moment, that compiles to If the results of a macro were expanded after the call returns, there would be no way for a macro to output a call to a function with the same name as a macro that is currently defined. In this case, the above would output If the This is why I'm thinking adding |
Thanks for the explanation. Perhaps this can be mitigated by setting proper expectations instead of code? Since macros share the same namespace as functions and are expanded before the code is run, the user should expect that a macro with the same name as a function will be expanded and the function will not be called. |
I think we will need a |
Macro expansion should be recursive In CommonLisp:
My understanding of macroexpand is that it's a debugging tool you call from the REPL to troubleshoot a macro, not something you would write in a production macro. I'm not even sure you can have a non-macro form w/ the same name as a macro form in any traditional lisp. In CL, for example, the last definition wins:
Sweet.js has a concept called In addition, Sweet.js has primitive support for modules, which allow you to define "private" macros. I use both of those sweet.js features in Mithril's template compiler (it's basically a function inliner that replaces |
Macro expansion definitely should be recursive. Common Lisp is a Lisp-2 (a namespace for variables, and one for functions + macros, which have very similar behavior, and can call one another... But arguably CL is pretty weird in its "everything-is-late-bound" behavior. Macroexpand(-1) are debugging tools, AFAIK (some implementations may give you even more advanced tools, like SBCL's |
Also, Sweet.js's "let macros" do some weird stuff to make the macro not expand... Only inside itself. It'll be expanded everywhere else normally, AFAIK |
FWIW, I just tried in Racket REPL and also got "last definition wins" behavior
Sweet.js let macros do feel like a bolted-on hack, but it's the only way I was able to output a variable called I think there is a subtle but important difference between lisp macros and sweet.js macros: lisp macros occupy space in the runtime's v-table, but sweet.js macros do not. The main implication is that if you can dynamically change a macro as is possible w/ a lisp REPL, then by definition you cannot have macros w/ the same name as runtime values. Conversely, to be able to output "shadowed" variables, macro-expansion-time must be at compile-time, and that must be distinctly separated from runtime. Given that eslisp doesn't aim to be a "true" lisp (in the dynamic code-is-always-data sense), perhaps an approach closer to sweetjs might make more sense. |
Even that stops somewhere. As I've heard, old lisps literally stored functions as cons cells, but that proved to be too slow for "real-life stuff". That's why later lisps change it |
I think macros are confusing enough we don't really need this though, do we? Care to explain your use case a bit more, maybe? I might've not understood it. |
My use case is that one of my API functions ( |
Oh, I see now. Well, but the |
Regardless of whether it's a lisp 1 or 2, there's still a difference between a runtime macro and a compile-time macro. Eslisp macros run at compile-time, so they become subjected to the possibility of variable shadowing. My suggestion, based off of what I said above, is to have |
As @stasm mentions in anko/eslisp-fancy-function#1, if a call to a user-defined macro is given as a parameter of another user-defined macro, it always compiles to a function call, not to the results of that macro.
Example:
Expected output:
Actual output:
This does not affect built-in macros, because they can access the compilation environment with which they can optionally compile their arguments (for example like this) in a way that resolves and executes macros.
User macros currently don't have that choice. They operate on lists they've received as arguments, but the process by which they're compiled doesn't take macros into account. If it did, we'd have the opposite problem. There needs to be a user choice.
To give an example, if
a
is a user-defined macro and it is called with(a (b))
, it receives 1 argument, which is an array containing an atomb
. If it returns that argument as-is, it is interpreted as a function callb();
, which the macro may have wanted to return. But another interpretation is to compile the list taking the macro table into account, in which case it may turn out thatb
is a macro that returns different code to be used there instead.There obviously should be no such limitation. I could use some help from more experienced lispers here. What would Batman do? Is this what
macroexpand
/macroexpand-all
are for?The text was updated successfully, but these errors were encountered: