-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Add macro verbatim blocks #6108
Conversation
This will be very helpful! |
@paulcsmith Cool! Will you use it in lucky? Where? |
Yep! Here are a few examples:
There are places in other Lucky libs as well 👍 |
@paulcsmith Yeah, I now think this feature might not be that useful after all... in the first snippet you have I'm starting to think this is all very similar to |
After looking at more of the Lucky code I think you're right. I might not actually use it much because as soon as I need something without a I much prefer Crystal's approach over |
|
The problem is that macros inside methods are only expanded on instantiation, which is a bit weird and inconsistent, but it's what makes macro methods work. And then if you need to define such method inside a macro, like I think I guess this is what we got and this is what we have to stick to... unfortunately. |
@paulcsmith Could you explain why you prefer Crystal's approach over Elixir's? Elixir is super consistent in this regard. |
@asterite It may be a purely syntactic thing and nothing to do with how quote/unquote works. I just find it extremely hard to mentally parse because it looks like any other method call, but it isn't. I've written a lot of Elixir so it's not just something that comes with time (at least for me). Using Overall, I don't think that With that said, if macros could keep the same |
@paulcsmith Cool, thanks for the reply! Yes, the idea of using Then using macros as templates make it really easy to conditionally insert an "if" depending on some condition. With Elixir I guess you'd have to build an AST, conditionally wrapping stuff with another AST node, etc, which would make the code harder to follow. But then again, consistency is a really good thing in Elixir. I wish we could somehow get rid of |
Yeah, that is pretty hairy. I've got a few like that myself. This may or may not be off-topic, but I think it's relevant to deciding what to prioritize. I think being able to call other macros inside https://gist.github.com/paulcsmith/7973eae4f6fa5584e7e26f4163d3b026 # Here is what I have now
def self.create{% if with_bang %}!{% end %}(
# Can't call another macro in here because it won't be expanded by the compiler
{% if with_params %}params,{% end %}
{% for type_declaration in (NEEDS_ON_CREATE + NEEDS_ON_INITIALIZE) %}
{{ type_declaration }},
{% end %}
{% if @type.constant :FIELDS %}
{% for field in FIELDS %}
{{ field[:name] }} : {{ field[:type] }} | Nothing{% if field[:nilable] %} | Nil{% end %} = Nothing.new,
{% end %}
{% end %}
{% for field in VIRTUAL_FIELDS %}
{{ field.var }} : {{ field.type }} | Nothing = Nothing.new,
{% end %}
)
# Here's what I'd *love* to do
def self.create{% if with_bang %}!{% end %}(
{% if with_params %}params,{% end %}
{{ args_for_needs() }}
{{ args_for_fields() }}
{{ args_for_virtual_fields() }}
# Or maybe a method like this
{{ run_macro args_for_fields() }}
)
macro args_for_needs
{% for type_declaration in (NEEDS_ON_CREATE + NEEDS_ON_INITIALIZE) %}
{{ type_declaration }},
{% end %}
end
macro args_for_fields
{% if @type.constant :FIELDS %}
{% for field in FIELDS %}
{{ field[:name] }} : {{ field[:type] }} | Nothing{% if field[:nilable] %} | Nil{% end %} = Nothing.new,
{% end %}
{% end %}
end
macro args_for_virtual_fields
{% for field in VIRTUAL_FIELDS %}
{{ field.var }} : {{ field.type }} | Nothing = Nothing.new,
{% end %}
end It would also be great to do this with EDIT: Maybe this would be hard to figure out scoping so possible fully qualifying things would work: |
(correct implementation of #6105... I should have not closed the other PR so fast :-P)
What
This adds the ability to specify verbatim blocks in macros, so macro code isn't immediately execute.
Why
Take a look at how the macro
def_clone
is implemented:Note all the
\{%
and\{{
in there. It's needed because we don't want to execute@type
right away: we want to defer it until the method is executed, so it works like a macro method. The\
is ugly, but it works.With this PR, we can write it like this:
verbatim
basically won't treat the block inside it as macro code, but instead as just a string that's being pasted. Then when theclone
andinitialize_copy
methods are invoked, the pasted code that's actually macro is executed.So this is just a better way to write and do what we can already do.
I also need this for a complete implementation of #6082 . The
JSON::Serialize
module must inject aninitialize
in the included type so that types referenced in attributes (likeconverter
) work well. For example:If
MyConverter
is pasted in the includedJSON::Serialize
module, it will give an error becauseMyConverter
can't be reached from that location (that's the reason why I had to remove all the private types in the specs in that PR). Ifinitalize
is injected with anincluded
macro,MyConverter
can be accessed.But since that injected
initialize
method already uses@type
in macros, I'll have to escape with\
all of that. But it's so much simpler to just surround everything with averbatim
block.