Discussion:
[groovy-dev] State of the macros
Cédric Champeau
2014-10-07 17:20:32 UTC
Permalink
Hi everyone,

You may know that we have long been complaining about the complexity of
AST transformations in Groovy. There are a very powerful tool, but it is
still a bit complex to handle. In Groovy 2.4, we planned to integrate
work from Sergei Egorov (@bsideup) that he called "Groovy macros".
Basically the idea is to have a simple way to replace classic method
class into more complex expressions. In this email, I'll try to
summarize the project and give some ideas about the orientations we want
for the Groovy language. In the end, your opinion matters so please feel
free to comment.

First of all, Sergei actually worked on two "macro" projects. The first
one consists of a "macro" block that can be illustrated by this:

defsomeVariable=newVariableExpression("someVariable");

ReturnStatementresult= macro {
return newNonExistingClass($v{someVariable});
}

As you can see, it is some kind of "super AstBuilder" which is capable
of handling variables/expressions from an external context thanks to
proper escaping with $v. This macro code is primarily aimed at being
used inside AST transformation themselves, and dramatically reduce the
amount of code required to generate an AST tree. An advantage of those
macro blocks is that if you use them in AST transformations, you're
actually not limited to expressions. You can generate anything, as long
as what is inside the macro { ... } block is supported by the Groovy
syntax. A drawback of the current implementation is that it uses a
global AST transformation. So as soon as you have the macro project on
classpath, every single method call corresponding to "macro" will be
interpreted as a macro block. There are multiple disadvantages of global
AST transformations. First of all, they are, as the name says, global,
meaning that they apply independently on *every* class being compiled by
Groovy. This also means that the transformation is run even if you know
the code you write is not using it. In particular, we need to inspect
the full AST, in-depth, to find potential method calls named "macro"
even if there's not a single one in the code (because you will only know
once you have visited the full AST). In short, a global AST
transformation has a clear performance impact, because it is executed
independently of the context. For the macro stuff, an easy workaround
would be to transform the global AST transformation into a local one.
For example, an AST transformation using the macro system could declare
it by annotating with @EnableMacros. I think it is reasonable, since it
is very unlikely that you would use the macro { ... } stuff in regular
code. The macro block is indeed useful, but in the end, it is limited to
writing other AST transformations that would be either global or local.
In short, the current macro { ... } block is useful for AST
transformation designers, but cannot be used by "regular" Groovy users
to define new language constructs.

In answer to that problem, Sergei worked on a second implementation of
macros named @Macro. The idea is both simple and elegant. Just like you
can define extension methods in Groovy (see
http://docs.groovy-lang.org/2.3.7/html/documentation/#_extension_modules),
you can write a macro like this:

public classTestMacroMethods {

@Macro
public staticExpression safe(MacroContext macroContext, MethodCallExpression callExpression) {
returnternaryX(
notNullX(callExpression.getObjectExpression()),
callExpression,
constX(null)
);
}
}

and for this to work, TestMacroMethods needs to be declared as a regular
Groovy extension module. In that case, *any* code using "safe" would be
transformed, so:

safe(x.foo()).bar()

will be expanded *at compile time* into x.foo?x.foo().bar():null

A more interesting example with pattern matching can be found here:
https://github.com/bsideup/groovy-pattern-match/blob/master/src/main/java/ru/trylogic/groovy/pattern/PatternMatchingMacroMethods.java

An important thing to understand is that the @Macro annotation is *not*
an AST transformation. Instead, it's a marker annotation which is looked
up at compile time, for each method call. So for each method call of the
AST, we try to find if an extension method node of the same name exists,
is annotated with @Macro and takes MacroContext as the first argument
and in that case, the original method call is transformed thanks to the
extension method at compile time. The code of the macro itself is
regular AST transformation code. It does *not* use the macro stuff from
the first implementation, hence doesn't simplify writing xforms. On the
other hand, it greatly simplifies the availability of transforms by
making them writable as simple extension methods, without the need of
ceremony (annotations). It is actually very cool, but it comes at a
price. Performance wise, it is still a global AST transformation, but
worse, for each and every call, it implies finding an extension method
node. It can (and will) cost a lot, but we can improve the situation by
introducing specific caches. Moreover, it also implies that the sole
fact of adding a macro "jar" on classpath could potentially affect the
semantics of your program: if a method call in your code matches the
name of a macro method call, then it would be applied at compile time.
Of course, one could argue that it is already the case for extension
modules, but the risk is lower: for extension modules, the only cases of
conflicts is when the receiver of the message matches the class of the
extension module. For @Macro, any method call on "implicit this" would
be matched. This draws the point of whether the @Macro stuff should also
be combined with a @UseMacro local AST transformation, in order to avoid
the problem of the global one.

If we do, then definitely, the performance issue is not one anymore,
because only marked code would be transformed. On the other hand, we
introduce ceremony again, which would mean that we loose the benefit of
extension methods being transparently visible. In the case of the
pattern matching stuff, this would mean that you would have to
explicitly annotate your code to enable the feature. Is it a problem?
I'm not sure actually. Also it's worth noting that the global AST
transformation issue is less of a problem if you have lots of macros on
classpath, because a single transformation would apply them all in a
single pass.

Last but not least, my feeling is that what we need is something in
between the first macro implementation and the second one. In
particular, I would like to be able to write the @Macro method body
using those macro { ... } blocks. It makes a lot of sense to me. Next, I
wouldn't like to be limited to expressions. I think a macro should be
able to produce any AST node. This implies expressions, but also
statements or even full methods.

I gave an example a little contrived, I admit, to Sergei, which is,
imagine that I want to generate two methods with the same body, but
accepting two different argument types. Then I could write a macro that
generates the method:

@Macro MethodNode createMultiply(MacroContext ctx, ClassNode argType) {
macro {
_argType_ multByTwo(_argType_ x) { 2 }
}
}

Then in a class I could write:

class Calculator {
createMultiply int
createMultiply double
}

Of course, this wouldn't compile because the grammar wouldn't allow
defining a method in a closure (in the macro block), and it would not
recognize the createMultiply calls directly in the class body, but I
kind of like the idea of a macro system that just allows expanding and
reasoning at the AST level anywhere, because it lets us create new
language constructs.

In any case, let us know what you think, what you expect from a macro
system in Groovy. We have very good starting points, let's make it rock
solid :-)
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
Sergei Egorov
2014-10-07 17:46:58 UTC
Permalink
Great brief, Cedric!

I'm here and ready to discuss both features.

Just want to add few things:

- MacroGroovy already can be used inside @Macro methods
- The reason why I'm created @Macro extension methods (MEMs) is simple:
I decided to extract code base from MacroGroovy, because it can be used
outside of "AST nodes generation" context.
- MEMs are implemented with Global AST transformation with method call
scanning, and it costs. But, there are already almost the same
transformation called AstBuilderTransformation. Most important, it can be
replaced with @Macro method with backward compatibility. So, it's not about
new global xform. Just rethinking of old one.
Post by Cédric Champeau
Hi everyone,
You may know that we have long been complaining about the complexity of
AST transformations in Groovy. There are a very powerful tool, but it is
still a bit complex to handle. In Groovy 2.4, we planned to integrate work
idea is to have a simple way to replace classic method class into more
complex expressions. In this email, I'll try to summarize the project and
give some ideas about the orientations we want for the Groovy language. In
the end, your opinion matters so please feel free to comment.
First of all, Sergei actually worked on two "macro" projects. The first
def someVariable = new VariableExpression("someVariable");
ReturnStatement result = macro {
return new NonExistingClass($v{someVariable});
}
As you can see, it is some kind of "super AstBuilder" which is capable of
handling variables/expressions from an external context thanks to proper
escaping with $v. This macro code is primarily aimed at being used inside
AST transformation themselves, and dramatically reduce the amount of code
required to generate an AST tree. An advantage of those macro blocks is
that if you use them in AST transformations, you're actually not limited to
expressions. You can generate anything, as long as what is inside the macro
{ ... } block is supported by the Groovy syntax. A drawback of the current
implementation is that it uses a global AST transformation. So as soon as
you have the macro project on classpath, every single method call
corresponding to "macro" will be interpreted as a macro block. There are
multiple disadvantages of global AST transformations. First of all, they
are, as the name says, global, meaning that they apply independently on
*every* class being compiled by Groovy. This also means that the
transformation is run even if you know the code you write is not using it.
In particular, we need to inspect the full AST, in-depth, to find potential
method calls named "macro" even if there's not a single one in the code
(because you will only know once you have visited the full AST). In short,
a global AST transformation has a clear performance impact, because it is
executed independently of the context. For the macro stuff, an easy
workaround would be to transform the global AST transformation into a local
one. For example, an AST transformation using the macro system could
since it is very unlikely that you would use the macro { ... } stuff in
regular code. The macro block is indeed useful, but in the end, it is
limited to writing other AST transformations that would be either global or
local. In short, the current macro { ... } block is useful for AST
transformation designers, but cannot be used by "regular" Groovy users to
define new language constructs.
In answer to that problem, Sergei worked on a second implementation of
define extension methods in Groovy (see
http://docs.groovy-lang.org/2.3.7/html/documentation/#_extension_modules),
public class TestMacroMethods {
@Macro public static Expression safe(MacroContext macroContext, MethodCallExpression callExpression) {
return ternaryX(
notNullX(callExpression.getObjectExpression()),
callExpression,
constX(null)
);
}
}
and for this to work, TestMacroMethods needs to be declared as a regular
Groovy extension module. In that case, *any* code using "safe" would be
safe(x.foo()).bar()
will be expanded *at compile time* into x.foo?x.foo().bar():null
https://github.com/bsideup/groovy-pattern-match/blob/master/src/main/java/ru/trylogic/groovy/pattern/PatternMatchingMacroMethods.java
AST transformation. Instead, it's a marker annotation which is looked up at
compile time, for each method call. So for each method call of the AST, we
try to find if an extension method node of the same name exists, is
that case, the original method call is transformed thanks to the extension
method at compile time. The code of the macro itself is regular AST
transformation code. It does *not* use the macro stuff from the first
implementation, hence doesn't simplify writing xforms. On the other hand,
it greatly simplifies the availability of transforms by making them
writable as simple extension methods, without the need of ceremony
(annotations). It is actually very cool, but it comes at a price.
Performance wise, it is still a global AST transformation, but worse, for
each and every call, it implies finding an extension method node. It can
(and will) cost a lot, but we can improve the situation by introducing
specific caches. Moreover, it also implies that the sole fact of adding a
macro "jar" on classpath could potentially affect the semantics of your
program: if a method call in your code matches the name of a macro method
call, then it would be applied at compile time. Of course, one could argue
for extension modules, the only cases of conflicts is when the receiver of
method call on "implicit this" would be matched. This draws the point of
transformation, in order to avoid the problem of the global one.
If we do, then definitely, the performance issue is not one anymore,
because only marked code would be transformed. On the other hand, we
introduce ceremony again, which would mean that we loose the benefit of
extension methods being transparently visible. In the case of the pattern
matching stuff, this would mean that you would have to explicitly annotate
your code to enable the feature. Is it a problem? I'm not sure actually.
Also it's worth noting that the global AST transformation issue is less of
a problem if you have lots of macros on classpath, because a single
transformation would apply them all in a single pass.
Last but not least, my feeling is that what we need is something in
between the first macro implementation and the second one. In particular, I
... } blocks. It makes a lot of sense to me. Next, I wouldn't like to be
limited to expressions. I think a macro should be able to produce any AST
node. This implies expressions, but also statements or even full methods.
I gave an example a little contrived, I admit, to Sergei, which is,
imagine that I want to generate two methods with the same body, but
accepting two different argument types. Then I could write a macro that
@Macro MethodNode createMultiply(MacroContext ctx, ClassNode argType) {
macro {
_argType_ multByTwo(_argType_ x) { 2 }
}
}
class Calculator {
createMultiply int
createMultiply double
}
Of course, this wouldn't compile because the grammar wouldn't allow
defining a method in a closure (in the macro block), and it would not
recognize the createMultiply calls directly in the class body, but I kind
of like the idea of a macro system that just allows expanding and reasoning
at the AST level anywhere, because it lets us create new language
constructs.
In any case, let us know what you think, what you expect from a macro
system in Groovy. We have very good starting points, let's make it rock
solid :-)
--
Cédric Champeau
SpringSource - Pivotalhttp://twitter.com/CedricChampeauhttp://melix.github.io/bloghttp://spring.io/ http://www.gopivotal.com/
--
Best regards,
Sergei Egorov
Thibault Kruse
2014-10-07 19:25:20 UTC
Permalink
With my conservative attitude, I would hope for minimal performance
impact and non-global application (@UseMacro or similar).

The maintenance effort required to maintain Groovy itself (And any
future library relying on this feature) should also be mentioned in
this kind of discussion.

With that in mind I would expect from a Groovy Macro system to either
cause little maintenance effort on Groovy itself, or cause a major
boost of success for Groovy, justifying the efforts.
Post by Sergei Egorov
Great brief, Cedric!
I'm here and ready to discuss both features.
decided to extract code base from MacroGroovy, because it can be used
outside of "AST nodes generation" context.
MEMs are implemented with Global AST transformation with method call
scanning, and it costs. But, there are already almost the same
transformation called AstBuilderTransformation. Most important, it can be
new global xform. Just rethinking of old one.
Post by Cédric Champeau
Hi everyone,
You may know that we have long been complaining about the complexity of
AST transformations in Groovy. There are a very powerful tool, but it is
still a bit complex to handle. In Groovy 2.4, we planned to integrate work
idea is to have a simple way to replace classic method class into more
complex expressions. In this email, I'll try to summarize the project and
give some ideas about the orientations we want for the Groovy language. In
the end, your opinion matters so please feel free to comment.
First of all, Sergei actually worked on two "macro" projects. The first
def someVariable = new VariableExpression("someVariable");
ReturnStatement result = macro {
return new NonExistingClass($v{someVariable});
}
As you can see, it is some kind of "super AstBuilder" which is capable of
handling variables/expressions from an external context thanks to proper
escaping with $v. This macro code is primarily aimed at being used inside
AST transformation themselves, and dramatically reduce the amount of code
required to generate an AST tree. An advantage of those macro blocks is that
if you use them in AST transformations, you're actually not limited to
expressions. You can generate anything, as long as what is inside the macro
{ ... } block is supported by the Groovy syntax. A drawback of the current
implementation is that it uses a global AST transformation. So as soon as
you have the macro project on classpath, every single method call
corresponding to "macro" will be interpreted as a macro block. There are
multiple disadvantages of global AST transformations. First of all, they
are, as the name says, global, meaning that they apply independently on
*every* class being compiled by Groovy. This also means that the
transformation is run even if you know the code you write is not using it.
In particular, we need to inspect the full AST, in-depth, to find potential
method calls named "macro" even if there's not a single one in the code
(because you will only know once you have visited the full AST). In short, a
global AST transformation has a clear performance impact, because it is
executed independently of the context. For the macro stuff, an easy
workaround would be to transform the global AST transformation into a local
one. For example, an AST transformation using the macro system could declare
very unlikely that you would use the macro { ... } stuff in regular code.
The macro block is indeed useful, but in the end, it is limited to writing
other AST transformations that would be either global or local. In short,
the current macro { ... } block is useful for AST transformation designers,
but cannot be used by "regular" Groovy users to define new language
constructs.
In answer to that problem, Sergei worked on a second implementation of
define extension methods in Groovy (see
http://docs.groovy-lang.org/2.3.7/html/documentation/#_extension_modules),
public class TestMacroMethods {
@Macro
public static Expression safe(MacroContext macroContext,
MethodCallExpression callExpression) {
return ternaryX(
notNullX(callExpression.getObjectExpression()),
callExpression,
constX(null)
);
}
}
and for this to work, TestMacroMethods needs to be declared as a regular
Groovy extension module. In that case, *any* code using "safe" would be
safe(x.foo()).bar()
will be expanded *at compile time* into x.foo?x.foo().bar():null
https://github.com/bsideup/groovy-pattern-match/blob/master/src/main/java/ru/trylogic/groovy/pattern/PatternMatchingMacroMethods.java
AST transformation. Instead, it's a marker annotation which is looked up at
compile time, for each method call. So for each method call of the AST, we
try to find if an extension method node of the same name exists, is
that case, the original method call is transformed thanks to the extension
method at compile time. The code of the macro itself is regular AST
transformation code. It does *not* use the macro stuff from the first
implementation, hence doesn't simplify writing xforms. On the other hand, it
greatly simplifies the availability of transforms by making them writable as
simple extension methods, without the need of ceremony (annotations). It is
actually very cool, but it comes at a price. Performance wise, it is still a
global AST transformation, but worse, for each and every call, it implies
finding an extension method node. It can (and will) cost a lot, but we can
improve the situation by introducing specific caches. Moreover, it also
implies that the sole fact of adding a macro "jar" on classpath could
potentially affect the semantics of your program: if a method call in your
code matches the name of a macro method call, then it would be applied at
compile time. Of course, one could argue that it is already the case for
extension modules, but the risk is lower: for extension modules, the only
cases of conflicts is when the receiver of the message matches the class of
problem of the global one.
If we do, then definitely, the performance issue is not one anymore,
because only marked code would be transformed. On the other hand, we
introduce ceremony again, which would mean that we loose the benefit of
extension methods being transparently visible. In the case of the pattern
matching stuff, this would mean that you would have to explicitly annotate
your code to enable the feature. Is it a problem? I'm not sure actually.
Also it's worth noting that the global AST transformation issue is less of a
problem if you have lots of macros on classpath, because a single
transformation would apply them all in a single pass.
Last but not least, my feeling is that what we need is something in
between the first macro implementation and the second one. In particular, I
... } blocks. It makes a lot of sense to me. Next, I wouldn't like to be
limited to expressions. I think a macro should be able to produce any AST
node. This implies expressions, but also statements or even full methods.
I gave an example a little contrived, I admit, to Sergei, which is,
imagine that I want to generate two methods with the same body, but
accepting two different argument types. Then I could write a macro that
@Macro MethodNode createMultiply(MacroContext ctx, ClassNode argType) {
macro {
_argType_ multByTwo(_argType_ x) { 2 }
}
}
class Calculator {
createMultiply int
createMultiply double
}
Of course, this wouldn't compile because the grammar wouldn't allow
defining a method in a closure (in the macro block), and it would not
recognize the createMultiply calls directly in the class body, but I kind of
like the idea of a macro system that just allows expanding and reasoning at
the AST level anywhere, because it lets us create new language constructs.
In any case, let us know what you think, what you expect from a macro
system in Groovy. We have very good starting points, let's make it rock
solid :-)
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
--
Best regards,
Sergei Egorov
---------------------------------------------------------------------
To unsubscribe from this list, please visit:

http://xircles.codehaus.org/manage_email
Sergei Egorov
2014-10-07 19:37:31 UTC
Permalink
Btw, great thing about macro system is modularity. It can be implemented as
subsystem and excluded from groovy-all dependency if your project doesn't
use macro extension methods.
Post by Thibault Kruse
With my conservative attitude, I would hope for minimal performance
The maintenance effort required to maintain Groovy itself (And any
future library relying on this feature) should also be mentioned in
this kind of discussion.
With that in mind I would expect from a Groovy Macro system to either
cause little maintenance effort on Groovy itself, or cause a major
boost of success for Groovy, justifying the efforts.
Post by Sergei Egorov
Great brief, Cedric!
I'm here and ready to discuss both features.
decided to extract code base from MacroGroovy, because it can be used
outside of "AST nodes generation" context.
MEMs are implemented with Global AST transformation with method call
scanning, and it costs. But, there are already almost the same
transformation called AstBuilderTransformation. Most important, it can be
about
Post by Sergei Egorov
new global xform. Just rethinking of old one.
On Tue, Oct 7, 2014 at 8:20 PM, Cédric Champeau <
Post by Cédric Champeau
Hi everyone,
You may know that we have long been complaining about the complexity of
AST transformations in Groovy. There are a very powerful tool, but it is
still a bit complex to handle. In Groovy 2.4, we planned to integrate
work
the
Post by Sergei Egorov
Post by Cédric Champeau
idea is to have a simple way to replace classic method class into more
complex expressions. In this email, I'll try to summarize the project
and
Post by Sergei Egorov
Post by Cédric Champeau
give some ideas about the orientations we want for the Groovy language.
In
Post by Sergei Egorov
Post by Cédric Champeau
the end, your opinion matters so please feel free to comment.
First of all, Sergei actually worked on two "macro" projects. The first
def someVariable = new VariableExpression("someVariable");
ReturnStatement result = macro {
return new NonExistingClass($v{someVariable});
}
As you can see, it is some kind of "super AstBuilder" which is capable
of
Post by Sergei Egorov
Post by Cédric Champeau
handling variables/expressions from an external context thanks to proper
escaping with $v. This macro code is primarily aimed at being used
inside
Post by Sergei Egorov
Post by Cédric Champeau
AST transformation themselves, and dramatically reduce the amount of
code
Post by Sergei Egorov
Post by Cédric Champeau
required to generate an AST tree. An advantage of those macro blocks is
that
Post by Sergei Egorov
Post by Cédric Champeau
if you use them in AST transformations, you're actually not limited to
expressions. You can generate anything, as long as what is inside the
macro
Post by Sergei Egorov
Post by Cédric Champeau
{ ... } block is supported by the Groovy syntax. A drawback of the
current
Post by Sergei Egorov
Post by Cédric Champeau
implementation is that it uses a global AST transformation. So as soon
as
Post by Sergei Egorov
Post by Cédric Champeau
you have the macro project on classpath, every single method call
corresponding to "macro" will be interpreted as a macro block. There are
multiple disadvantages of global AST transformations. First of all, they
are, as the name says, global, meaning that they apply independently on
*every* class being compiled by Groovy. This also means that the
transformation is run even if you know the code you write is not using
it.
Post by Sergei Egorov
Post by Cédric Champeau
In particular, we need to inspect the full AST, in-depth, to find
potential
Post by Sergei Egorov
Post by Cédric Champeau
method calls named "macro" even if there's not a single one in the code
(because you will only know once you have visited the full AST). In
short, a
Post by Sergei Egorov
Post by Cédric Champeau
global AST transformation has a clear performance impact, because it is
executed independently of the context. For the macro stuff, an easy
workaround would be to transform the global AST transformation into a
local
Post by Sergei Egorov
Post by Cédric Champeau
one. For example, an AST transformation using the macro system could
declare
is
Post by Sergei Egorov
Post by Cédric Champeau
very unlikely that you would use the macro { ... } stuff in regular
code.
Post by Sergei Egorov
Post by Cédric Champeau
The macro block is indeed useful, but in the end, it is limited to
writing
Post by Sergei Egorov
Post by Cédric Champeau
other AST transformations that would be either global or local. In
short,
Post by Sergei Egorov
Post by Cédric Champeau
the current macro { ... } block is useful for AST transformation
designers,
Post by Sergei Egorov
Post by Cédric Champeau
but cannot be used by "regular" Groovy users to define new language
constructs.
In answer to that problem, Sergei worked on a second implementation of
can
Post by Sergei Egorov
Post by Cédric Champeau
define extension methods in Groovy (see
http://docs.groovy-lang.org/2.3.7/html/documentation/#_extension_modules),
Post by Sergei Egorov
Post by Cédric Champeau
public class TestMacroMethods {
@Macro
public static Expression safe(MacroContext macroContext,
MethodCallExpression callExpression) {
return ternaryX(
notNullX(callExpression.getObjectExpression()),
callExpression,
constX(null)
);
}
}
and for this to work, TestMacroMethods needs to be declared as a regular
Groovy extension module. In that case, *any* code using "safe" would be
safe(x.foo()).bar()
will be expanded *at compile time* into x.foo?x.foo().bar():null
https://github.com/bsideup/groovy-pattern-match/blob/master/src/main/java/ru/trylogic/groovy/pattern/PatternMatchingMacroMethods.java
an
Post by Sergei Egorov
Post by Cédric Champeau
AST transformation. Instead, it's a marker annotation which is looked
up at
Post by Sergei Egorov
Post by Cédric Champeau
compile time, for each method call. So for each method call of the AST,
we
Post by Sergei Egorov
Post by Cédric Champeau
try to find if an extension method node of the same name exists, is
in
Post by Sergei Egorov
Post by Cédric Champeau
that case, the original method call is transformed thanks to the
extension
Post by Sergei Egorov
Post by Cédric Champeau
method at compile time. The code of the macro itself is regular AST
transformation code. It does *not* use the macro stuff from the first
implementation, hence doesn't simplify writing xforms. On the other
hand, it
Post by Sergei Egorov
Post by Cédric Champeau
greatly simplifies the availability of transforms by making them
writable as
Post by Sergei Egorov
Post by Cédric Champeau
simple extension methods, without the need of ceremony (annotations).
It is
Post by Sergei Egorov
Post by Cédric Champeau
actually very cool, but it comes at a price. Performance wise, it is
still a
Post by Sergei Egorov
Post by Cédric Champeau
global AST transformation, but worse, for each and every call, it
implies
Post by Sergei Egorov
Post by Cédric Champeau
finding an extension method node. It can (and will) cost a lot, but we
can
Post by Sergei Egorov
Post by Cédric Champeau
improve the situation by introducing specific caches. Moreover, it also
implies that the sole fact of adding a macro "jar" on classpath could
potentially affect the semantics of your program: if a method call in
your
Post by Sergei Egorov
Post by Cédric Champeau
code matches the name of a macro method call, then it would be applied
at
Post by Sergei Egorov
Post by Cédric Champeau
compile time. Of course, one could argue that it is already the case for
extension modules, but the risk is lower: for extension modules, the
only
Post by Sergei Egorov
Post by Cédric Champeau
cases of conflicts is when the receiver of the message matches the
class of
would
also be
the
Post by Sergei Egorov
Post by Cédric Champeau
problem of the global one.
If we do, then definitely, the performance issue is not one anymore,
because only marked code would be transformed. On the other hand, we
introduce ceremony again, which would mean that we loose the benefit of
extension methods being transparently visible. In the case of the
pattern
Post by Sergei Egorov
Post by Cédric Champeau
matching stuff, this would mean that you would have to explicitly
annotate
Post by Sergei Egorov
Post by Cédric Champeau
your code to enable the feature. Is it a problem? I'm not sure actually.
Also it's worth noting that the global AST transformation issue is less
of a
Post by Sergei Egorov
Post by Cédric Champeau
problem if you have lots of macros on classpath, because a single
transformation would apply them all in a single pass.
Last but not least, my feeling is that what we need is something in
between the first macro implementation and the second one. In
particular, I
{
Post by Sergei Egorov
Post by Cédric Champeau
... } blocks. It makes a lot of sense to me. Next, I wouldn't like to be
limited to expressions. I think a macro should be able to produce any
AST
Post by Sergei Egorov
Post by Cédric Champeau
node. This implies expressions, but also statements or even full
methods.
Post by Sergei Egorov
Post by Cédric Champeau
I gave an example a little contrived, I admit, to Sergei, which is,
imagine that I want to generate two methods with the same body, but
accepting two different argument types. Then I could write a macro that
@Macro MethodNode createMultiply(MacroContext ctx, ClassNode argType) {
macro {
_argType_ multByTwo(_argType_ x) { 2 }
}
}
class Calculator {
createMultiply int
createMultiply double
}
Of course, this wouldn't compile because the grammar wouldn't allow
defining a method in a closure (in the macro block), and it would not
recognize the createMultiply calls directly in the class body, but I
kind of
Post by Sergei Egorov
Post by Cédric Champeau
like the idea of a macro system that just allows expanding and
reasoning at
Post by Sergei Egorov
Post by Cédric Champeau
the AST level anywhere, because it lets us create new language
constructs.
Post by Sergei Egorov
Post by Cédric Champeau
In any case, let us know what you think, what you expect from a macro
system in Groovy. We have very good starting points, let's make it rock
solid :-)
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
--
Best regards,
Sergei Egorov
---------------------------------------------------------------------
http://xircles.codehaus.org/manage_email
--
Best regards,
Sergei Egorov
Cédric Champeau
2014-10-10 14:49:18 UTC
Permalink
Excluding from groovy-all would be an option, but it would also defeat
the concept of -all :-)
Post by Sergei Egorov
Btw, great thing about macro system is modularity. It can be
implemented as subsystem and excluded from groovy-all dependency if
your project doesn't use macro extension methods.
On Tue, Oct 7, 2014 at 10:25 PM, Thibault Kruse
With my conservative attitude, I would hope for minimal performance
The maintenance effort required to maintain Groovy itself (And any
future library relying on this feature) should also be mentioned in
this kind of discussion.
With that in mind I would expect from a Groovy Macro system to either
cause little maintenance effort on Groovy itself, or cause a major
boost of success for Groovy, justifying the efforts.
Post by Sergei Egorov
Great brief, Cedric!
I'm here and ready to discuss both features.
simple: I
Post by Sergei Egorov
decided to extract code base from MacroGroovy, because it can be
used
Post by Sergei Egorov
outside of "AST nodes generation" context.
MEMs are implemented with Global AST transformation with method call
scanning, and it costs. But, there are already almost the same
transformation called AstBuilderTransformation. Most important,
it can be
it's not about
Post by Sergei Egorov
new global xform. Just rethinking of old one.
On Tue, Oct 7, 2014 at 8:20 PM, Cédric Champeau
Post by Cédric Champeau
Hi everyone,
You may know that we have long been complaining about the
complexity of
Post by Sergei Egorov
Post by Cédric Champeau
AST transformations in Groovy. There are a very powerful tool,
but it is
Post by Sergei Egorov
Post by Cédric Champeau
still a bit complex to handle. In Groovy 2.4, we planned to
integrate work
Basically the
Post by Sergei Egorov
Post by Cédric Champeau
idea is to have a simple way to replace classic method class
into more
Post by Sergei Egorov
Post by Cédric Champeau
complex expressions. In this email, I'll try to summarize the
project and
Post by Sergei Egorov
Post by Cédric Champeau
give some ideas about the orientations we want for the Groovy
language. In
Post by Sergei Egorov
Post by Cédric Champeau
the end, your opinion matters so please feel free to comment.
First of all, Sergei actually worked on two "macro" projects.
The first
Post by Sergei Egorov
Post by Cédric Champeau
def someVariable = new VariableExpression("someVariable");
ReturnStatement result = macro {
return new NonExistingClass($v{someVariable});
}
As you can see, it is some kind of "super AstBuilder" which is
capable of
Post by Sergei Egorov
Post by Cédric Champeau
handling variables/expressions from an external context thanks
to proper
Post by Sergei Egorov
Post by Cédric Champeau
escaping with $v. This macro code is primarily aimed at being
used inside
Post by Sergei Egorov
Post by Cédric Champeau
AST transformation themselves, and dramatically reduce the
amount of code
Post by Sergei Egorov
Post by Cédric Champeau
required to generate an AST tree. An advantage of those macro
blocks is that
Post by Sergei Egorov
Post by Cédric Champeau
if you use them in AST transformations, you're actually not
limited to
Post by Sergei Egorov
Post by Cédric Champeau
expressions. You can generate anything, as long as what is
inside the macro
Post by Sergei Egorov
Post by Cédric Champeau
{ ... } block is supported by the Groovy syntax. A drawback of
the current
Post by Sergei Egorov
Post by Cédric Champeau
implementation is that it uses a global AST transformation. So
as soon as
Post by Sergei Egorov
Post by Cédric Champeau
you have the macro project on classpath, every single method call
corresponding to "macro" will be interpreted as a macro block.
There are
Post by Sergei Egorov
Post by Cédric Champeau
multiple disadvantages of global AST transformations. First of
all, they
Post by Sergei Egorov
Post by Cédric Champeau
are, as the name says, global, meaning that they apply
independently on
Post by Sergei Egorov
Post by Cédric Champeau
*every* class being compiled by Groovy. This also means that the
transformation is run even if you know the code you write is
not using it.
Post by Sergei Egorov
Post by Cédric Champeau
In particular, we need to inspect the full AST, in-depth, to
find potential
Post by Sergei Egorov
Post by Cédric Champeau
method calls named "macro" even if there's not a single one in
the code
Post by Sergei Egorov
Post by Cédric Champeau
(because you will only know once you have visited the full
AST). In short, a
Post by Sergei Egorov
Post by Cédric Champeau
global AST transformation has a clear performance impact,
because it is
Post by Sergei Egorov
Post by Cédric Champeau
executed independently of the context. For the macro stuff, an easy
workaround would be to transform the global AST transformation
into a local
Post by Sergei Egorov
Post by Cédric Champeau
one. For example, an AST transformation using the macro system
could declare
since it is
Post by Sergei Egorov
Post by Cédric Champeau
very unlikely that you would use the macro { ... } stuff in
regular code.
Post by Sergei Egorov
Post by Cédric Champeau
The macro block is indeed useful, but in the end, it is limited
to writing
Post by Sergei Egorov
Post by Cédric Champeau
other AST transformations that would be either global or local.
In short,
Post by Sergei Egorov
Post by Cédric Champeau
the current macro { ... } block is useful for AST
transformation designers,
Post by Sergei Egorov
Post by Cédric Champeau
but cannot be used by "regular" Groovy users to define new language
constructs.
In answer to that problem, Sergei worked on a second
implementation of
like you can
Post by Sergei Egorov
Post by Cédric Champeau
define extension methods in Groovy (see
http://docs.groovy-lang.org/2.3.7/html/documentation/#_extension_modules),
Post by Sergei Egorov
Post by Cédric Champeau
public class TestMacroMethods {
@Macro
public static Expression safe(MacroContext macroContext,
MethodCallExpression callExpression) {
return ternaryX(
notNullX(callExpression.getObjectExpression()),
callExpression,
constX(null)
);
}
}
and for this to work, TestMacroMethods needs to be declared as
a regular
Post by Sergei Egorov
Post by Cédric Champeau
Groovy extension module. In that case, *any* code using "safe"
would be
Post by Sergei Egorov
Post by Cédric Champeau
safe(x.foo()).bar()
will be expanded *at compile time* into x.foo?x.foo().bar():null
https://github.com/bsideup/groovy-pattern-match/blob/master/src/main/java/ru/trylogic/groovy/pattern/PatternMatchingMacroMethods.java
is *not* an
Post by Sergei Egorov
Post by Cédric Champeau
AST transformation. Instead, it's a marker annotation which is
looked up at
Post by Sergei Egorov
Post by Cédric Champeau
compile time, for each method call. So for each method call of
the AST, we
Post by Sergei Egorov
Post by Cédric Champeau
try to find if an extension method node of the same name exists, is
argument and in
Post by Sergei Egorov
Post by Cédric Champeau
that case, the original method call is transformed thanks to
the extension
Post by Sergei Egorov
Post by Cédric Champeau
method at compile time. The code of the macro itself is regular AST
transformation code. It does *not* use the macro stuff from the
first
Post by Sergei Egorov
Post by Cédric Champeau
implementation, hence doesn't simplify writing xforms. On the
other hand, it
Post by Sergei Egorov
Post by Cédric Champeau
greatly simplifies the availability of transforms by making
them writable as
Post by Sergei Egorov
Post by Cédric Champeau
simple extension methods, without the need of ceremony
(annotations). It is
Post by Sergei Egorov
Post by Cédric Champeau
actually very cool, but it comes at a price. Performance wise,
it is still a
Post by Sergei Egorov
Post by Cédric Champeau
global AST transformation, but worse, for each and every call,
it implies
Post by Sergei Egorov
Post by Cédric Champeau
finding an extension method node. It can (and will) cost a lot,
but we can
Post by Sergei Egorov
Post by Cédric Champeau
improve the situation by introducing specific caches. Moreover,
it also
Post by Sergei Egorov
Post by Cédric Champeau
implies that the sole fact of adding a macro "jar" on classpath
could
Post by Sergei Egorov
Post by Cédric Champeau
potentially affect the semantics of your program: if a method
call in your
Post by Sergei Egorov
Post by Cédric Champeau
code matches the name of a macro method call, then it would be
applied at
Post by Sergei Egorov
Post by Cédric Champeau
compile time. Of course, one could argue that it is already the
case for
Post by Sergei Egorov
Post by Cédric Champeau
extension modules, but the risk is lower: for extension
modules, the only
Post by Sergei Egorov
Post by Cédric Champeau
cases of conflicts is when the receiver of the message matches
the class of
this" would
should also be
avoid the
Post by Sergei Egorov
Post by Cédric Champeau
problem of the global one.
If we do, then definitely, the performance issue is not one
anymore,
Post by Sergei Egorov
Post by Cédric Champeau
because only marked code would be transformed. On the other
hand, we
Post by Sergei Egorov
Post by Cédric Champeau
introduce ceremony again, which would mean that we loose the
benefit of
Post by Sergei Egorov
Post by Cédric Champeau
extension methods being transparently visible. In the case of
the pattern
Post by Sergei Egorov
Post by Cédric Champeau
matching stuff, this would mean that you would have to
explicitly annotate
Post by Sergei Egorov
Post by Cédric Champeau
your code to enable the feature. Is it a problem? I'm not sure
actually.
Post by Sergei Egorov
Post by Cédric Champeau
Also it's worth noting that the global AST transformation issue
is less of a
Post by Sergei Egorov
Post by Cédric Champeau
problem if you have lots of macros on classpath, because a single
transformation would apply them all in a single pass.
Last but not least, my feeling is that what we need is something in
between the first macro implementation and the second one. In
particular, I
those macro {
Post by Sergei Egorov
Post by Cédric Champeau
... } blocks. It makes a lot of sense to me. Next, I wouldn't
like to be
Post by Sergei Egorov
Post by Cédric Champeau
limited to expressions. I think a macro should be able to
produce any AST
Post by Sergei Egorov
Post by Cédric Champeau
node. This implies expressions, but also statements or even
full methods.
Post by Sergei Egorov
Post by Cédric Champeau
I gave an example a little contrived, I admit, to Sergei, which is,
imagine that I want to generate two methods with the same body, but
accepting two different argument types. Then I could write a
macro that
Post by Sergei Egorov
Post by Cédric Champeau
@Macro MethodNode createMultiply(MacroContext ctx, ClassNode
argType) {
Post by Sergei Egorov
Post by Cédric Champeau
macro {
_argType_ multByTwo(_argType_ x) { 2 }
}
}
class Calculator {
createMultiply int
createMultiply double
}
Of course, this wouldn't compile because the grammar wouldn't allow
defining a method in a closure (in the macro block), and it
would not
Post by Sergei Egorov
Post by Cédric Champeau
recognize the createMultiply calls directly in the class body,
but I kind of
Post by Sergei Egorov
Post by Cédric Champeau
like the idea of a macro system that just allows expanding and
reasoning at
Post by Sergei Egorov
Post by Cédric Champeau
the AST level anywhere, because it lets us create new language
constructs.
Post by Sergei Egorov
Post by Cédric Champeau
In any case, let us know what you think, what you expect from a
macro
Post by Sergei Egorov
Post by Cédric Champeau
system in Groovy. We have very good starting points, let's make
it rock
Post by Sergei Egorov
Post by Cédric Champeau
solid :-)
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
--
Best regards,
Sergei Egorov
---------------------------------------------------------------------
http://xircles.codehaus.org/manage_email
--
Best regards,
Sergei Egorov
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
Sergei Egorov
2014-10-11 11:00:27 UTC
Permalink
I mean something like performance recommendation "If you do not use macro
system in your project, you can exclude it to improve compilation time" :)
Post by Cédric Champeau
Excluding from groovy-all would be an option, but it would also defeat
the concept of -all :-)
Btw, great thing about macro system is modularity. It can be implemented
as subsystem and excluded from groovy-all dependency if your project
doesn't use macro extension methods.
Post by Thibault Kruse
With my conservative attitude, I would hope for minimal performance
The maintenance effort required to maintain Groovy itself (And any
future library relying on this feature) should also be mentioned in
this kind of discussion.
With that in mind I would expect from a Groovy Macro system to either
cause little maintenance effort on Groovy itself, or cause a major
boost of success for Groovy, justifying the efforts.
Post by Sergei Egorov
Great brief, Cedric!
I'm here and ready to discuss both features.
decided to extract code base from MacroGroovy, because it can be used
outside of "AST nodes generation" context.
MEMs are implemented with Global AST transformation with method call
scanning, and it costs. But, there are already almost the same
transformation called AstBuilderTransformation. Most important, it can
be
about
Post by Sergei Egorov
new global xform. Just rethinking of old one.
On Tue, Oct 7, 2014 at 8:20 PM, Cédric Champeau <
Post by Cédric Champeau
Hi everyone,
You may know that we have long been complaining about the complexity of
AST transformations in Groovy. There are a very powerful tool, but it
is
Post by Sergei Egorov
Post by Cédric Champeau
still a bit complex to handle. In Groovy 2.4, we planned to integrate
work
Basically the
Post by Sergei Egorov
Post by Cédric Champeau
idea is to have a simple way to replace classic method class into more
complex expressions. In this email, I'll try to summarize the project
and
Post by Sergei Egorov
Post by Cédric Champeau
give some ideas about the orientations we want for the Groovy
language. In
Post by Sergei Egorov
Post by Cédric Champeau
the end, your opinion matters so please feel free to comment.
First of all, Sergei actually worked on two "macro" projects. The first
def someVariable = new VariableExpression("someVariable");
ReturnStatement result = macro {
return new NonExistingClass($v{someVariable});
}
As you can see, it is some kind of "super AstBuilder" which is capable
of
Post by Sergei Egorov
Post by Cédric Champeau
handling variables/expressions from an external context thanks to
proper
Post by Sergei Egorov
Post by Cédric Champeau
escaping with $v. This macro code is primarily aimed at being used
inside
Post by Sergei Egorov
Post by Cédric Champeau
AST transformation themselves, and dramatically reduce the amount of
code
Post by Sergei Egorov
Post by Cédric Champeau
required to generate an AST tree. An advantage of those macro blocks
is that
Post by Sergei Egorov
Post by Cédric Champeau
if you use them in AST transformations, you're actually not limited to
expressions. You can generate anything, as long as what is inside the
macro
Post by Sergei Egorov
Post by Cédric Champeau
{ ... } block is supported by the Groovy syntax. A drawback of the
current
Post by Sergei Egorov
Post by Cédric Champeau
implementation is that it uses a global AST transformation. So as soon
as
Post by Sergei Egorov
Post by Cédric Champeau
you have the macro project on classpath, every single method call
corresponding to "macro" will be interpreted as a macro block. There
are
Post by Sergei Egorov
Post by Cédric Champeau
multiple disadvantages of global AST transformations. First of all,
they
Post by Sergei Egorov
Post by Cédric Champeau
are, as the name says, global, meaning that they apply independently on
*every* class being compiled by Groovy. This also means that the
transformation is run even if you know the code you write is not using
it.
Post by Sergei Egorov
Post by Cédric Champeau
In particular, we need to inspect the full AST, in-depth, to find
potential
Post by Sergei Egorov
Post by Cédric Champeau
method calls named "macro" even if there's not a single one in the code
(because you will only know once you have visited the full AST). In
short, a
Post by Sergei Egorov
Post by Cédric Champeau
global AST transformation has a clear performance impact, because it is
executed independently of the context. For the macro stuff, an easy
workaround would be to transform the global AST transformation into a
local
Post by Sergei Egorov
Post by Cédric Champeau
one. For example, an AST transformation using the macro system could
declare
it is
Post by Sergei Egorov
Post by Cédric Champeau
very unlikely that you would use the macro { ... } stuff in regular
code.
Post by Sergei Egorov
Post by Cédric Champeau
The macro block is indeed useful, but in the end, it is limited to
writing
Post by Sergei Egorov
Post by Cédric Champeau
other AST transformations that would be either global or local. In
short,
Post by Sergei Egorov
Post by Cédric Champeau
the current macro { ... } block is useful for AST transformation
designers,
Post by Sergei Egorov
Post by Cédric Champeau
but cannot be used by "regular" Groovy users to define new language
constructs.
In answer to that problem, Sergei worked on a second implementation of
you can
Post by Sergei Egorov
Post by Cédric Champeau
define extension methods in Groovy (see
http://docs.groovy-lang.org/2.3.7/html/documentation/#_extension_modules
),
Post by Sergei Egorov
Post by Cédric Champeau
public class TestMacroMethods {
@Macro
public static Expression safe(MacroContext macroContext,
MethodCallExpression callExpression) {
return ternaryX(
notNullX(callExpression.getObjectExpression()),
callExpression,
constX(null)
);
}
}
and for this to work, TestMacroMethods needs to be declared as a
regular
Post by Sergei Egorov
Post by Cédric Champeau
Groovy extension module. In that case, *any* code using "safe" would be
safe(x.foo()).bar()
will be expanded *at compile time* into x.foo?x.foo().bar():null
https://github.com/bsideup/groovy-pattern-match/blob/master/src/main/java/ru/trylogic/groovy/pattern/PatternMatchingMacroMethods.java
*not* an
Post by Sergei Egorov
Post by Cédric Champeau
AST transformation. Instead, it's a marker annotation which is looked
up at
Post by Sergei Egorov
Post by Cédric Champeau
compile time, for each method call. So for each method call of the
AST, we
Post by Sergei Egorov
Post by Cédric Champeau
try to find if an extension method node of the same name exists, is
in
Post by Sergei Egorov
Post by Cédric Champeau
that case, the original method call is transformed thanks to the
extension
Post by Sergei Egorov
Post by Cédric Champeau
method at compile time. The code of the macro itself is regular AST
transformation code. It does *not* use the macro stuff from the first
implementation, hence doesn't simplify writing xforms. On the other
hand, it
Post by Sergei Egorov
Post by Cédric Champeau
greatly simplifies the availability of transforms by making them
writable as
Post by Sergei Egorov
Post by Cédric Champeau
simple extension methods, without the need of ceremony (annotations).
It is
Post by Sergei Egorov
Post by Cédric Champeau
actually very cool, but it comes at a price. Performance wise, it is
still a
Post by Sergei Egorov
Post by Cédric Champeau
global AST transformation, but worse, for each and every call, it
implies
Post by Sergei Egorov
Post by Cédric Champeau
finding an extension method node. It can (and will) cost a lot, but we
can
Post by Sergei Egorov
Post by Cédric Champeau
improve the situation by introducing specific caches. Moreover, it also
implies that the sole fact of adding a macro "jar" on classpath could
potentially affect the semantics of your program: if a method call in
your
Post by Sergei Egorov
Post by Cédric Champeau
code matches the name of a macro method call, then it would be applied
at
Post by Sergei Egorov
Post by Cédric Champeau
compile time. Of course, one could argue that it is already the case
for
Post by Sergei Egorov
Post by Cédric Champeau
extension modules, but the risk is lower: for extension modules, the
only
Post by Sergei Egorov
Post by Cédric Champeau
cases of conflicts is when the receiver of the message matches the
class of
would
also be
the
Post by Sergei Egorov
Post by Cédric Champeau
problem of the global one.
If we do, then definitely, the performance issue is not one anymore,
because only marked code would be transformed. On the other hand, we
introduce ceremony again, which would mean that we loose the benefit of
extension methods being transparently visible. In the case of the
pattern
Post by Sergei Egorov
Post by Cédric Champeau
matching stuff, this would mean that you would have to explicitly
annotate
Post by Sergei Egorov
Post by Cédric Champeau
your code to enable the feature. Is it a problem? I'm not sure
actually.
Post by Sergei Egorov
Post by Cédric Champeau
Also it's worth noting that the global AST transformation issue is
less of a
Post by Sergei Egorov
Post by Cédric Champeau
problem if you have lots of macros on classpath, because a single
transformation would apply them all in a single pass.
Last but not least, my feeling is that what we need is something in
between the first macro implementation and the second one. In
particular, I
macro {
Post by Sergei Egorov
Post by Cédric Champeau
... } blocks. It makes a lot of sense to me. Next, I wouldn't like to
be
Post by Sergei Egorov
Post by Cédric Champeau
limited to expressions. I think a macro should be able to produce any
AST
Post by Sergei Egorov
Post by Cédric Champeau
node. This implies expressions, but also statements or even full
methods.
Post by Sergei Egorov
Post by Cédric Champeau
I gave an example a little contrived, I admit, to Sergei, which is,
imagine that I want to generate two methods with the same body, but
accepting two different argument types. Then I could write a macro that
@Macro MethodNode createMultiply(MacroContext ctx, ClassNode argType) {
macro {
_argType_ multByTwo(_argType_ x) { 2 }
}
}
class Calculator {
createMultiply int
createMultiply double
}
Of course, this wouldn't compile because the grammar wouldn't allow
defining a method in a closure (in the macro block), and it would not
recognize the createMultiply calls directly in the class body, but I
kind of
Post by Sergei Egorov
Post by Cédric Champeau
like the idea of a macro system that just allows expanding and
reasoning at
Post by Sergei Egorov
Post by Cédric Champeau
the AST level anywhere, because it lets us create new language
constructs.
Post by Sergei Egorov
Post by Cédric Champeau
In any case, let us know what you think, what you expect from a macro
system in Groovy. We have very good starting points, let's make it rock
solid :-)
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
--
Best regards,
Sergei Egorov
---------------------------------------------------------------------
http://xircles.codehaus.org/manage_email
--
Best regards,
Sergei Egorov
--
Cédric Champeau
SpringSource - Pivotalhttp://twitter.com/CedricChampeauhttp://melix.github.io/bloghttp://spring.io/ http://www.gopivotal.com/
--
Best regards,
Sergei Egorov
Sergei Egorov
2014-10-11 11:01:45 UTC
Permalink
Since we have compiler configuration, we can also add "disable macro
system" flag too
Post by Sergei Egorov
I mean something like performance recommendation "If you do not use macro
system in your project, you can exclude it to improve compilation time" :)
On Fri, Oct 10, 2014 at 5:49 PM, Cédric Champeau <
Post by Cédric Champeau
Excluding from groovy-all would be an option, but it would also defeat
the concept of -all :-)
Btw, great thing about macro system is modularity. It can be implemented
as subsystem and excluded from groovy-all dependency if your project
doesn't use macro extension methods.
Post by Thibault Kruse
With my conservative attitude, I would hope for minimal performance
The maintenance effort required to maintain Groovy itself (And any
future library relying on this feature) should also be mentioned in
this kind of discussion.
With that in mind I would expect from a Groovy Macro system to either
cause little maintenance effort on Groovy itself, or cause a major
boost of success for Groovy, justifying the efforts.
Post by Sergei Egorov
Great brief, Cedric!
I'm here and ready to discuss both features.
decided to extract code base from MacroGroovy, because it can be used
outside of "AST nodes generation" context.
MEMs are implemented with Global AST transformation with method call
scanning, and it costs. But, there are already almost the same
transformation called AstBuilderTransformation. Most important, it can
be
about
Post by Sergei Egorov
new global xform. Just rethinking of old one.
On Tue, Oct 7, 2014 at 8:20 PM, Cédric Champeau <
Post by Cédric Champeau
Hi everyone,
You may know that we have long been complaining about the complexity
of
Post by Sergei Egorov
Post by Cédric Champeau
AST transformations in Groovy. There are a very powerful tool, but it
is
Post by Sergei Egorov
Post by Cédric Champeau
still a bit complex to handle. In Groovy 2.4, we planned to integrate
work
Basically the
Post by Sergei Egorov
Post by Cédric Champeau
idea is to have a simple way to replace classic method class into more
complex expressions. In this email, I'll try to summarize the project
and
Post by Sergei Egorov
Post by Cédric Champeau
give some ideas about the orientations we want for the Groovy
language. In
Post by Sergei Egorov
Post by Cédric Champeau
the end, your opinion matters so please feel free to comment.
First of all, Sergei actually worked on two "macro" projects. The
first
Post by Sergei Egorov
Post by Cédric Champeau
def someVariable = new VariableExpression("someVariable");
ReturnStatement result = macro {
return new NonExistingClass($v{someVariable});
}
As you can see, it is some kind of "super AstBuilder" which is
capable of
Post by Sergei Egorov
Post by Cédric Champeau
handling variables/expressions from an external context thanks to
proper
Post by Sergei Egorov
Post by Cédric Champeau
escaping with $v. This macro code is primarily aimed at being used
inside
Post by Sergei Egorov
Post by Cédric Champeau
AST transformation themselves, and dramatically reduce the amount of
code
Post by Sergei Egorov
Post by Cédric Champeau
required to generate an AST tree. An advantage of those macro blocks
is that
Post by Sergei Egorov
Post by Cédric Champeau
if you use them in AST transformations, you're actually not limited to
expressions. You can generate anything, as long as what is inside the
macro
Post by Sergei Egorov
Post by Cédric Champeau
{ ... } block is supported by the Groovy syntax. A drawback of the
current
Post by Sergei Egorov
Post by Cédric Champeau
implementation is that it uses a global AST transformation. So as
soon as
Post by Sergei Egorov
Post by Cédric Champeau
you have the macro project on classpath, every single method call
corresponding to "macro" will be interpreted as a macro block. There
are
Post by Sergei Egorov
Post by Cédric Champeau
multiple disadvantages of global AST transformations. First of all,
they
Post by Sergei Egorov
Post by Cédric Champeau
are, as the name says, global, meaning that they apply independently
on
Post by Sergei Egorov
Post by Cédric Champeau
*every* class being compiled by Groovy. This also means that the
transformation is run even if you know the code you write is not
using it.
Post by Sergei Egorov
Post by Cédric Champeau
In particular, we need to inspect the full AST, in-depth, to find
potential
Post by Sergei Egorov
Post by Cédric Champeau
method calls named "macro" even if there's not a single one in the
code
Post by Sergei Egorov
Post by Cédric Champeau
(because you will only know once you have visited the full AST). In
short, a
Post by Sergei Egorov
Post by Cédric Champeau
global AST transformation has a clear performance impact, because it
is
Post by Sergei Egorov
Post by Cédric Champeau
executed independently of the context. For the macro stuff, an easy
workaround would be to transform the global AST transformation into a
local
Post by Sergei Egorov
Post by Cédric Champeau
one. For example, an AST transformation using the macro system could
declare
it is
Post by Sergei Egorov
Post by Cédric Champeau
very unlikely that you would use the macro { ... } stuff in regular
code.
Post by Sergei Egorov
Post by Cédric Champeau
The macro block is indeed useful, but in the end, it is limited to
writing
Post by Sergei Egorov
Post by Cédric Champeau
other AST transformations that would be either global or local. In
short,
Post by Sergei Egorov
Post by Cédric Champeau
the current macro { ... } block is useful for AST transformation
designers,
Post by Sergei Egorov
Post by Cédric Champeau
but cannot be used by "regular" Groovy users to define new language
constructs.
In answer to that problem, Sergei worked on a second implementation of
you can
Post by Sergei Egorov
Post by Cédric Champeau
define extension methods in Groovy (see
http://docs.groovy-lang.org/2.3.7/html/documentation/#_extension_modules
),
Post by Sergei Egorov
Post by Cédric Champeau
public class TestMacroMethods {
@Macro
public static Expression safe(MacroContext macroContext,
MethodCallExpression callExpression) {
return ternaryX(
notNullX(callExpression.getObjectExpression()),
callExpression,
constX(null)
);
}
}
and for this to work, TestMacroMethods needs to be declared as a
regular
Post by Sergei Egorov
Post by Cédric Champeau
Groovy extension module. In that case, *any* code using "safe" would
be
Post by Sergei Egorov
Post by Cédric Champeau
safe(x.foo()).bar()
will be expanded *at compile time* into x.foo?x.foo().bar():null
https://github.com/bsideup/groovy-pattern-match/blob/master/src/main/java/ru/trylogic/groovy/pattern/PatternMatchingMacroMethods.java
*not* an
Post by Sergei Egorov
Post by Cédric Champeau
AST transformation. Instead, it's a marker annotation which is looked
up at
Post by Sergei Egorov
Post by Cédric Champeau
compile time, for each method call. So for each method call of the
AST, we
Post by Sergei Egorov
Post by Cédric Champeau
try to find if an extension method node of the same name exists, is
and in
Post by Sergei Egorov
Post by Cédric Champeau
that case, the original method call is transformed thanks to the
extension
Post by Sergei Egorov
Post by Cédric Champeau
method at compile time. The code of the macro itself is regular AST
transformation code. It does *not* use the macro stuff from the first
implementation, hence doesn't simplify writing xforms. On the other
hand, it
Post by Sergei Egorov
Post by Cédric Champeau
greatly simplifies the availability of transforms by making them
writable as
Post by Sergei Egorov
Post by Cédric Champeau
simple extension methods, without the need of ceremony (annotations).
It is
Post by Sergei Egorov
Post by Cédric Champeau
actually very cool, but it comes at a price. Performance wise, it is
still a
Post by Sergei Egorov
Post by Cédric Champeau
global AST transformation, but worse, for each and every call, it
implies
Post by Sergei Egorov
Post by Cédric Champeau
finding an extension method node. It can (and will) cost a lot, but
we can
Post by Sergei Egorov
Post by Cédric Champeau
improve the situation by introducing specific caches. Moreover, it
also
Post by Sergei Egorov
Post by Cédric Champeau
implies that the sole fact of adding a macro "jar" on classpath could
potentially affect the semantics of your program: if a method call in
your
Post by Sergei Egorov
Post by Cédric Champeau
code matches the name of a macro method call, then it would be
applied at
Post by Sergei Egorov
Post by Cédric Champeau
compile time. Of course, one could argue that it is already the case
for
Post by Sergei Egorov
Post by Cédric Champeau
extension modules, but the risk is lower: for extension modules, the
only
Post by Sergei Egorov
Post by Cédric Champeau
cases of conflicts is when the receiver of the message matches the
class of
would
also be
the
Post by Sergei Egorov
Post by Cédric Champeau
problem of the global one.
If we do, then definitely, the performance issue is not one anymore,
because only marked code would be transformed. On the other hand, we
introduce ceremony again, which would mean that we loose the benefit
of
Post by Sergei Egorov
Post by Cédric Champeau
extension methods being transparently visible. In the case of the
pattern
Post by Sergei Egorov
Post by Cédric Champeau
matching stuff, this would mean that you would have to explicitly
annotate
Post by Sergei Egorov
Post by Cédric Champeau
your code to enable the feature. Is it a problem? I'm not sure
actually.
Post by Sergei Egorov
Post by Cédric Champeau
Also it's worth noting that the global AST transformation issue is
less of a
Post by Sergei Egorov
Post by Cédric Champeau
problem if you have lots of macros on classpath, because a single
transformation would apply them all in a single pass.
Last but not least, my feeling is that what we need is something in
between the first macro implementation and the second one. In
particular, I
macro {
Post by Sergei Egorov
Post by Cédric Champeau
... } blocks. It makes a lot of sense to me. Next, I wouldn't like to
be
Post by Sergei Egorov
Post by Cédric Champeau
limited to expressions. I think a macro should be able to produce any
AST
Post by Sergei Egorov
Post by Cédric Champeau
node. This implies expressions, but also statements or even full
methods.
Post by Sergei Egorov
Post by Cédric Champeau
I gave an example a little contrived, I admit, to Sergei, which is,
imagine that I want to generate two methods with the same body, but
accepting two different argument types. Then I could write a macro
that
Post by Sergei Egorov
Post by Cédric Champeau
@Macro MethodNode createMultiply(MacroContext ctx, ClassNode argType)
{
Post by Sergei Egorov
Post by Cédric Champeau
macro {
_argType_ multByTwo(_argType_ x) { 2 }
}
}
class Calculator {
createMultiply int
createMultiply double
}
Of course, this wouldn't compile because the grammar wouldn't allow
defining a method in a closure (in the macro block), and it would not
recognize the createMultiply calls directly in the class body, but I
kind of
Post by Sergei Egorov
Post by Cédric Champeau
like the idea of a macro system that just allows expanding and
reasoning at
Post by Sergei Egorov
Post by Cédric Champeau
the AST level anywhere, because it lets us create new language
constructs.
Post by Sergei Egorov
Post by Cédric Champeau
In any case, let us know what you think, what you expect from a macro
system in Groovy. We have very good starting points, let's make it
rock
Post by Sergei Egorov
Post by Cédric Champeau
solid :-)
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
--
Best regards,
Sergei Egorov
---------------------------------------------------------------------
http://xircles.codehaus.org/manage_email
--
Best regards,
Sergei Egorov
--
Cédric Champeau
SpringSource - Pivotalhttp://twitter.com/CedricChampeauhttp://melix.github.io/bloghttp://spring.io/ http://www.gopivotal.com/
--
Best regards,
Sergei Egorov
--
Best regards,
Sergei Egorov
Cédric Champeau
2014-10-13 08:08:22 UTC
Permalink
In general we tend to avoid adding new compilation configuration
options, especially if they are related to AST xforms. Plus it has a
major disadvantage, which is that you either have to compile your
classes with --configscript, which is AFAIK not supported by IDEs so far
(Gradle supports it though), or you have to compile everything through
GroovyShell or GroovyClassLoader & friends.
Post by Sergei Egorov
Since we have compiler configuration, we can also add "disable macro
system" flag too
I mean something like performance recommendation "If you do not
use macro system in your project, you can exclude it to improve
compilation time" :)
On Fri, Oct 10, 2014 at 5:49 PM, Cédric Champeau
Excluding from groovy-all would be an option, but it would
also defeat the concept of -all :-)
Post by Sergei Egorov
Btw, great thing about macro system is modularity. It can be
implemented as subsystem and excluded from groovy-all
dependency if your project doesn't use macro extension methods.
On Tue, Oct 7, 2014 at 10:25 PM, Thibault Kruse
With my conservative attitude, I would hope for minimal
performance
The maintenance effort required to maintain Groovy itself
(And any
future library relying on this feature) should also be
mentioned in
this kind of discussion.
With that in mind I would expect from a Groovy Macro
system to either
cause little maintenance effort on Groovy itself, or
cause a major
boost of success for Groovy, justifying the efforts.
On Tue, Oct 7, 2014 at 7:46 PM, Sergei Egorov
Post by Sergei Egorov
Great brief, Cedric!
I'm here and ready to discuss both features.
(MEMs) is simple: I
Post by Sergei Egorov
decided to extract code base from MacroGroovy, because
it can be used
Post by Sergei Egorov
outside of "AST nodes generation" context.
MEMs are implemented with Global AST transformation
with method call
Post by Sergei Egorov
scanning, and it costs. But, there are already almost
the same
Post by Sergei Egorov
transformation called AstBuilderTransformation. Most
important, it can be
compatibility. So, it's not about
Post by Sergei Egorov
new global xform. Just rethinking of old one.
On Tue, Oct 7, 2014 at 8:20 PM, Cédric Champeau
Post by Cédric Champeau
Hi everyone,
You may know that we have long been complaining about
the complexity of
Post by Sergei Egorov
Post by Cédric Champeau
AST transformations in Groovy. There are a very
powerful tool, but it is
Post by Sergei Egorov
Post by Cédric Champeau
still a bit complex to handle. In Groovy 2.4, we
planned to integrate work
macros". Basically the
Post by Sergei Egorov
Post by Cédric Champeau
idea is to have a simple way to replace classic method
class into more
Post by Sergei Egorov
Post by Cédric Champeau
complex expressions. In this email, I'll try to
summarize the project and
Post by Sergei Egorov
Post by Cédric Champeau
give some ideas about the orientations we want for the
Groovy language. In
Post by Sergei Egorov
Post by Cédric Champeau
the end, your opinion matters so please feel free to
comment.
Post by Sergei Egorov
Post by Cédric Champeau
First of all, Sergei actually worked on two "macro"
projects. The first
Post by Sergei Egorov
Post by Cédric Champeau
one consists of a "macro" block that can be
def someVariable = new VariableExpression("someVariable");
ReturnStatement result = macro {
return new NonExistingClass($v{someVariable});
}
As you can see, it is some kind of "super AstBuilder"
which is capable of
Post by Sergei Egorov
Post by Cédric Champeau
handling variables/expressions from an external
context thanks to proper
Post by Sergei Egorov
Post by Cédric Champeau
escaping with $v. This macro code is primarily aimed
at being used inside
Post by Sergei Egorov
Post by Cédric Champeau
AST transformation themselves, and dramatically reduce
the amount of code
Post by Sergei Egorov
Post by Cédric Champeau
required to generate an AST tree. An advantage of
those macro blocks is that
Post by Sergei Egorov
Post by Cédric Champeau
if you use them in AST transformations, you're
actually not limited to
Post by Sergei Egorov
Post by Cédric Champeau
expressions. You can generate anything, as long as
what is inside the macro
Post by Sergei Egorov
Post by Cédric Champeau
{ ... } block is supported by the Groovy syntax. A
drawback of the current
Post by Sergei Egorov
Post by Cédric Champeau
implementation is that it uses a global AST
transformation. So as soon as
Post by Sergei Egorov
Post by Cédric Champeau
you have the macro project on classpath, every single
method call
Post by Sergei Egorov
Post by Cédric Champeau
corresponding to "macro" will be interpreted as a
macro block. There are
Post by Sergei Egorov
Post by Cédric Champeau
multiple disadvantages of global AST transformations.
First of all, they
Post by Sergei Egorov
Post by Cédric Champeau
are, as the name says, global, meaning that they apply
independently on
Post by Sergei Egorov
Post by Cédric Champeau
*every* class being compiled by Groovy. This also
means that the
Post by Sergei Egorov
Post by Cédric Champeau
transformation is run even if you know the code you
write is not using it.
Post by Sergei Egorov
Post by Cédric Champeau
In particular, we need to inspect the full AST,
in-depth, to find potential
Post by Sergei Egorov
Post by Cédric Champeau
method calls named "macro" even if there's not a
single one in the code
Post by Sergei Egorov
Post by Cédric Champeau
(because you will only know once you have visited the
full AST). In short, a
Post by Sergei Egorov
Post by Cédric Champeau
global AST transformation has a clear performance
impact, because it is
Post by Sergei Egorov
Post by Cédric Champeau
executed independently of the context. For the macro
stuff, an easy
Post by Sergei Egorov
Post by Cédric Champeau
workaround would be to transform the global AST
transformation into a local
Post by Sergei Egorov
Post by Cédric Champeau
one. For example, an AST transformation using the
macro system could declare
reasonable, since it is
Post by Sergei Egorov
Post by Cédric Champeau
very unlikely that you would use the macro { ... }
stuff in regular code.
Post by Sergei Egorov
Post by Cédric Champeau
The macro block is indeed useful, but in the end, it
is limited to writing
Post by Sergei Egorov
Post by Cédric Champeau
other AST transformations that would be either global
or local. In short,
Post by Sergei Egorov
Post by Cédric Champeau
the current macro { ... } block is useful for AST
transformation designers,
Post by Sergei Egorov
Post by Cédric Champeau
but cannot be used by "regular" Groovy users to define
new language
Post by Sergei Egorov
Post by Cédric Champeau
constructs.
In answer to that problem, Sergei worked on a second
implementation of
elegant. Just like you can
Post by Sergei Egorov
Post by Cédric Champeau
define extension methods in Groovy (see
http://docs.groovy-lang.org/2.3.7/html/documentation/#_extension_modules),
Post by Sergei Egorov
Post by Cédric Champeau
public class TestMacroMethods {
@Macro
public static Expression safe(MacroContext
macroContext,
Post by Sergei Egorov
Post by Cédric Champeau
MethodCallExpression callExpression) {
return ternaryX(
notNullX(callExpression.getObjectExpression()),
callExpression,
constX(null)
);
}
}
and for this to work, TestMacroMethods needs to be
declared as a regular
Post by Sergei Egorov
Post by Cédric Champeau
Groovy extension module. In that case, *any* code
using "safe" would be
Post by Sergei Egorov
Post by Cédric Champeau
safe(x.foo()).bar()
will be expanded *at compile time* into
x.foo?x.foo().bar():null
Post by Sergei Egorov
Post by Cédric Champeau
A more interesting example with pattern matching can
https://github.com/bsideup/groovy-pattern-match/blob/master/src/main/java/ru/trylogic/groovy/pattern/PatternMatchingMacroMethods.java
annotation is *not* an
Post by Sergei Egorov
Post by Cédric Champeau
AST transformation. Instead, it's a marker annotation
which is looked up at
Post by Sergei Egorov
Post by Cédric Champeau
compile time, for each method call. So for each method
call of the AST, we
Post by Sergei Egorov
Post by Cédric Champeau
try to find if an extension method node of the same
name exists, is
first argument and in
Post by Sergei Egorov
Post by Cédric Champeau
that case, the original method call is transformed
thanks to the extension
Post by Sergei Egorov
Post by Cédric Champeau
method at compile time. The code of the macro itself
is regular AST
Post by Sergei Egorov
Post by Cédric Champeau
transformation code. It does *not* use the macro stuff
from the first
Post by Sergei Egorov
Post by Cédric Champeau
implementation, hence doesn't simplify writing xforms.
On the other hand, it
Post by Sergei Egorov
Post by Cédric Champeau
greatly simplifies the availability of transforms by
making them writable as
Post by Sergei Egorov
Post by Cédric Champeau
simple extension methods, without the need of ceremony
(annotations). It is
Post by Sergei Egorov
Post by Cédric Champeau
actually very cool, but it comes at a price.
Performance wise, it is still a
Post by Sergei Egorov
Post by Cédric Champeau
global AST transformation, but worse, for each and
every call, it implies
Post by Sergei Egorov
Post by Cédric Champeau
finding an extension method node. It can (and will)
cost a lot, but we can
Post by Sergei Egorov
Post by Cédric Champeau
improve the situation by introducing specific caches.
Moreover, it also
Post by Sergei Egorov
Post by Cédric Champeau
implies that the sole fact of adding a macro "jar" on
classpath could
Post by Sergei Egorov
Post by Cédric Champeau
potentially affect the semantics of your program: if a
method call in your
Post by Sergei Egorov
Post by Cédric Champeau
code matches the name of a macro method call, then it
would be applied at
Post by Sergei Egorov
Post by Cédric Champeau
compile time. Of course, one could argue that it is
already the case for
Post by Sergei Egorov
Post by Cédric Champeau
extension modules, but the risk is lower: for
extension modules, the only
Post by Sergei Egorov
Post by Cédric Champeau
cases of conflicts is when the receiver of the message
matches the class of
"implicit this" would
stuff should also be
order to avoid the
Post by Sergei Egorov
Post by Cédric Champeau
problem of the global one.
If we do, then definitely, the performance issue is
not one anymore,
Post by Sergei Egorov
Post by Cédric Champeau
because only marked code would be transformed. On the
other hand, we
Post by Sergei Egorov
Post by Cédric Champeau
introduce ceremony again, which would mean that we
loose the benefit of
Post by Sergei Egorov
Post by Cédric Champeau
extension methods being transparently visible. In the
case of the pattern
Post by Sergei Egorov
Post by Cédric Champeau
matching stuff, this would mean that you would have to
explicitly annotate
Post by Sergei Egorov
Post by Cédric Champeau
your code to enable the feature. Is it a problem? I'm
not sure actually.
Post by Sergei Egorov
Post by Cédric Champeau
Also it's worth noting that the global AST
transformation issue is less of a
Post by Sergei Egorov
Post by Cédric Champeau
problem if you have lots of macros on classpath,
because a single
Post by Sergei Egorov
Post by Cédric Champeau
transformation would apply them all in a single pass.
Last but not least, my feeling is that what we need is
something in
Post by Sergei Egorov
Post by Cédric Champeau
between the first macro implementation and the second
one. In particular, I
using those macro {
Post by Sergei Egorov
Post by Cédric Champeau
... } blocks. It makes a lot of sense to me. Next, I
wouldn't like to be
Post by Sergei Egorov
Post by Cédric Champeau
limited to expressions. I think a macro should be able
to produce any AST
Post by Sergei Egorov
Post by Cédric Champeau
node. This implies expressions, but also statements or
even full methods.
Post by Sergei Egorov
Post by Cédric Champeau
I gave an example a little contrived, I admit, to
Sergei, which is,
Post by Sergei Egorov
Post by Cédric Champeau
imagine that I want to generate two methods with the
same body, but
Post by Sergei Egorov
Post by Cédric Champeau
accepting two different argument types. Then I could
write a macro that
Post by Sergei Egorov
Post by Cédric Champeau
@Macro MethodNode createMultiply(MacroContext ctx,
ClassNode argType) {
Post by Sergei Egorov
Post by Cédric Champeau
macro {
_argType_ multByTwo(_argType_ x) { 2 }
}
}
class Calculator {
createMultiply int
createMultiply double
}
Of course, this wouldn't compile because the grammar
wouldn't allow
Post by Sergei Egorov
Post by Cédric Champeau
defining a method in a closure (in the macro block),
and it would not
Post by Sergei Egorov
Post by Cédric Champeau
recognize the createMultiply calls directly in the
class body, but I kind of
Post by Sergei Egorov
Post by Cédric Champeau
like the idea of a macro system that just allows
expanding and reasoning at
Post by Sergei Egorov
Post by Cédric Champeau
the AST level anywhere, because it lets us create new
language constructs.
Post by Sergei Egorov
Post by Cédric Champeau
In any case, let us know what you think, what you
expect from a macro
Post by Sergei Egorov
Post by Cédric Champeau
system in Groovy. We have very good starting points,
let's make it rock
Post by Sergei Egorov
Post by Cédric Champeau
solid :-)
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
--
Best regards,
Sergei Egorov
---------------------------------------------------------------------
http://xircles.codehaus.org/manage_email
--
Best regards,
Sergei Egorov
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
--
Best regards,
Sergei Egorov
--
Best regards,
Sergei Egorov
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
Cédric Champeau
2014-10-17 14:37:11 UTC
Permalink
So here is the update for the end of the week. I worked with Sergei on
improving the macro system on two aspects:

1. being able to generate classes with macros
2. providing an AST matcher

I think 2 is very important if we want to simplify AST xforms.
Basically, if you need an AST transform to trigger on a specific
condition (like matching a method call of a certain form), it can be
very complicated and require a lot of boilerplate code. So the idea
would be to provide AST matching, where you would describe what you look
for, and the matching engine would return the list of AST nodes that
match. The good news is that I have a prototype working, and you can
already take a look at some code samples here:
https://github.com/melix/groovy-core/blob/d42fd1b39c5c421daf6cacb45403f8dcb06f8f12/subprojects/groovy-macro/src/test/groovy/org/codehaus/groovy/macro/matcher/ASTMatcherTest.groovy#L35

The API is far from being final, and as an example, I wrote an example
that illustrates what it could, in the end, look like:

def ast = macro {

println((a*b)+(a*c))

}

use(ASTMatcher) {

def pattern = macro {

(a*b)+(a*c)

}

def replacement = macro {

a*(b+c)

}

def result = ast.replace(pattern).with(replacement)

def expected = macro {

println (a*(b+c))

}

assert result.matches(expected)

}



What do you think?

Last but not least, the more I work with macros, the less I feed the
need for the global AST transformation. I think annotating the code that
uses the "macro" block with a local AST xform would be enough. Thoughts?
Post by Sergei Egorov
Great brief, Cedric!
I'm here and ready to discuss both features.
simple: I decided to extract code base from MacroGroovy, because
it can be used outside of "AST nodes generation" context.
* MEMs are implemented with Global AST transformation with method
call scanning, and it costs. But, there are already almost the
same transformation called AstBuilderTransformation. Most
backward compatibility. So, it's not about new global xform. Just
rethinking of old one.
On Tue, Oct 7, 2014 at 8:20 PM, Cédric Champeau
Hi everyone,
You may know that we have long been complaining about the
complexity of AST transformations in Groovy. There are a very
powerful tool, but it is still a bit complex to handle. In Groovy
that he called "Groovy macros". Basically the idea is to have a
simple way to replace classic method class into more complex
expressions. In this email, I'll try to summarize the project and
give some ideas about the orientations we want for the Groovy
language. In the end, your opinion matters so please feel free to
comment.
First of all, Sergei actually worked on two "macro" projects. The
defsomeVariable=newVariableExpression("someVariable");
ReturnStatementresult= macro {
return newNonExistingClass($v{someVariable});
}
As you can see, it is some kind of "super AstBuilder" which is
capable of handling variables/expressions from an external context
thanks to proper escaping with $v. This macro code is primarily
aimed at being used inside AST transformation themselves, and
dramatically reduce the amount of code required to generate an AST
tree. An advantage of those macro blocks is that if you use them
in AST transformations, you're actually not limited to
expressions. You can generate anything, as long as what is inside
the macro { ... } block is supported by the Groovy syntax. A
drawback of the current implementation is that it uses a global
AST transformation. So as soon as you have the macro project on
classpath, every single method call corresponding to "macro" will
be interpreted as a macro block. There are multiple disadvantages
of global AST transformations. First of all, they are, as the name
says, global, meaning that they apply independently on *every*
class being compiled by Groovy. This also means that the
transformation is run even if you know the code you write is not
using it. In particular, we need to inspect the full AST,
in-depth, to find potential method calls named "macro" even if
there's not a single one in the code (because you will only know
once you have visited the full AST). In short, a global AST
transformation has a clear performance impact, because it is
executed independently of the context. For the macro stuff, an
easy workaround would be to transform the global AST
transformation into a local one. For example, an AST
transformation using the macro system could declare it by
is very unlikely that you would use the macro { ... } stuff in
regular code. The macro block is indeed useful, but in the end, it
is limited to writing other AST transformations that would be
either global or local. In short, the current macro { ... } block
is useful for AST transformation designers, but cannot be used by
"regular" Groovy users to define new language constructs.
In answer to that problem, Sergei worked on a second
elegant. Just like you can define extension methods in Groovy (see
http://docs.groovy-lang.org/2.3.7/html/documentation/#_extension_modules),
public classTestMacroMethods {
@Macro
public staticExpression safe(MacroContext macroContext, MethodCallExpression callExpression) {
returnternaryX(
notNullX(callExpression.getObjectExpression()),
callExpression,
constX(null)
);
}
}
and for this to work, TestMacroMethods needs to be declared as a
regular Groovy extension module. In that case, *any* code using
safe(x.foo()).bar()
will be expanded *at compile time* into x.foo?x.foo().bar():null
https://github.com/bsideup/groovy-pattern-match/blob/master/src/main/java/ru/trylogic/groovy/pattern/PatternMatchingMacroMethods.java
*not* an AST transformation. Instead, it's a marker annotation
which is looked up at compile time, for each method call. So for
each method call of the AST, we try to find if an extension method
MacroContext as the first argument and in that case, the original
method call is transformed thanks to the extension method at
compile time. The code of the macro itself is regular AST
transformation code. It does *not* use the macro stuff from the
first implementation, hence doesn't simplify writing xforms. On
the other hand, it greatly simplifies the availability of
transforms by making them writable as simple extension methods,
without the need of ceremony (annotations). It is actually very
cool, but it comes at a price. Performance wise, it is still a
global AST transformation, but worse, for each and every call, it
implies finding an extension method node. It can (and will) cost a
lot, but we can improve the situation by introducing specific
caches. Moreover, it also implies that the sole fact of adding a
macro "jar" on classpath could potentially affect the semantics of
your program: if a method call in your code matches the name of a
macro method call, then it would be applied at compile time. Of
course, one could argue that it is already the case for extension
modules, but the risk is lower: for extension modules, the only
cases of conflicts is when the receiver of the message matches the
"implicit this" would be matched. This draws the point of whether
AST transformation, in order to avoid the problem of the global one.
If we do, then definitely, the performance issue is not one
anymore, because only marked code would be transformed. On the
other hand, we introduce ceremony again, which would mean that we
loose the benefit of extension methods being transparently
visible. In the case of the pattern matching stuff, this would
mean that you would have to explicitly annotate your code to
enable the feature. Is it a problem? I'm not sure actually. Also
it's worth noting that the global AST transformation issue is less
of a problem if you have lots of macros on classpath, because a
single transformation would apply them all in a single pass.
Last but not least, my feeling is that what we need is something
in between the first macro implementation and the second one. In
body using those macro { ... } blocks. It makes a lot of sense to
me. Next, I wouldn't like to be limited to expressions. I think a
macro should be able to produce any AST node. This implies
expressions, but also statements or even full methods.
I gave an example a little contrived, I admit, to Sergei, which
is, imagine that I want to generate two methods with the same
body, but accepting two different argument types. Then I could
@Macro MethodNode createMultiply(MacroContext ctx, ClassNode argType) {
macro {
_argType_ multByTwo(_argType_ x) { 2 }
}
}
class Calculator {
createMultiply int
createMultiply double
}
Of course, this wouldn't compile because the grammar wouldn't
allow defining a method in a closure (in the macro block), and it
would not recognize the createMultiply calls directly in the class
body, but I kind of like the idea of a macro system that just
allows expanding and reasoning at the AST level anywhere, because
it lets us create new language constructs.
In any case, let us know what you think, what you expect from a
macro system in Groovy. We have very good starting points, let's
make it rock solid :-)
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
--
Best regards,
Sergei Egorov
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
ngalarneau-a7zLCEouqeN+cjeuK/
2014-10-17 17:34:08 UTC
Permalink
Cedric,

That looks exciting!

What limitations do you foresee?


Neil




From: Cédric Champeau <cedric.champeau-***@public.gmane.org>
To: dev-i9PBDF1N6cxnkHa44VUL00B+***@public.gmane.org,
Date: 10/17/2014 12:43 PM
Subject: Re: [groovy-dev] State of the macros



So here is the update for the end of the week. I worked with Sergei on
improving the macro system on two aspects:

1. being able to generate classes with macros
2. providing an AST matcher

I think 2 is very important if we want to simplify AST xforms. Basically,
if you need an AST transform to trigger on a specific condition (like
matching a method call of a certain form), it can be very complicated and
require a lot of boilerplate code. So the idea would be to provide AST
matching, where you would describe what you look for, and the matching
engine would return the list of AST nodes that match. The good news is
that I have a prototype working, and you can already take a look at some
code samples here:
https://github.com/melix/groovy-core/blob/d42fd1b39c5c421daf6cacb45403f8dcb06f8f12/subprojects/groovy-macro/src/test/groovy/org/codehaus/groovy/macro/matcher/ASTMatcherTest.groovy#L35


The API is far from being final, and as an example, I wrote an example
that illustrates what it could, in the end, look like:

def ast = macro {

println((a*b)+(a*c))

}

use(ASTMatcher) {

def pattern = macro {

(a*b)+(a*c)

}

def replacement = macro {

a*(b+c)

}

def result = ast.replace(pattern).with(replacement)

def expected = macro {

println (a*(b+c))

}

assert result.matches(expected)

}



What do you think?

Last but not least, the more I work with macros, the less I feed the need
for the global AST transformation. I think annotating the code that uses
the "macro" block with a local AST xform would be enough. Thoughts?


On 07/10/2014 19:46, Sergei Egorov wrote:
Great brief, Cedric!

I'm here and ready to discuss both features.

Just want to add few things:
MacroGroovy already can be used inside @Macro methods
The reason why I'm created @Macro extension methods (MEMs) is simple: I
decided to extract code base from MacroGroovy, because it can be used
outside of "AST nodes generation" context.
MEMs are implemented with Global AST transformation with method call
scanning, and it costs. But, there are already almost the same
transformation called AstBuilderTransformation. Most important, it can be
replaced with @Macro method with backward compatibility. So, it's not
about new global xform. Just rethinking of old one.
Hi everyone,

You may know that we have long been complaining about the complexity of
AST transformations in Groovy. There are a very powerful tool, but it is
still a bit complex to handle. In Groovy 2.4, we planned to integrate work
from Sergei Egorov (@bsideup) that he called "Groovy macros". Basically
the idea is to have a simple way to replace classic method class into more
complex expressions. In this email, I'll try to summarize the project and
give some ideas about the orientations we want for the Groovy language. In
the end, your opinion matters so please feel free to comment.

First of all, Sergei actually worked on two "macro" projects. The first
one consists of a "macro" block that can be illustrated by this:
def someVariable = new VariableExpression("someVariable");

ReturnStatement result = macro {
return new NonExistingClass($v{someVariable});
}

As you can see, it is some kind of "super AstBuilder" which is capable of
handling variables/expressions from an external context thanks to proper
escaping with $v. This macro code is primarily aimed at being used inside
AST transformation themselves, and dramatically reduce the amount of code
required to generate an AST tree. An advantage of those macro blocks is
that if you use them in AST transformations, you're actually not limited
to expressions. You can generate anything, as long as what is inside the
macro { ... } block is supported by the Groovy syntax. A drawback of the
current implementation is that it uses a global AST transformation. So as
soon as you have the macro project on classpath, every single method call
corresponding to "macro" will be interpreted as a macro block. There are
multiple disadvantages of global AST transformations. First of all, they
are, as the name says, global, meaning that they apply independently on
*every* class being compiled by Groovy. This also means that the
transformation is run even if you know the code you write is not using it.
In particular, we need to inspect the full AST, in-depth, to find
potential method calls named "macro" even if there's not a single one in
the code (because you will only know once you have visited the full AST).
In short, a global AST transformation has a clear performance impact,
because it is executed independently of the context. For the macro stuff,
an easy workaround would be to transform the global AST transformation
into a local one. For example, an AST transformation using the macro
system could declare it by annotating with @EnableMacros. I think it is
reasonable, since it is very unlikely that you would use the macro { ... }
stuff in regular code. The macro block is indeed useful, but in the end,
it is limited to writing other AST transformations that would be either
global or local. In short, the current macro { ... } block is useful for
AST transformation designers, but cannot be used by "regular" Groovy users
to define new language constructs.

In answer to that problem, Sergei worked on a second implementation of
macros named @Macro. The idea is both simple and elegant. Just like you
can define extension methods in Groovy (see
http://docs.groovy-lang.org/2.3.7/html/documentation/#_extension_modules),
you can write a macro like this:

public class TestMacroMethods {

@Macro
public static Expression safe(MacroContext macroContext,
MethodCallExpression callExpression) {
return ternaryX(
notNullX(callExpression.getObjectExpression()),
callExpression,
constX(null)
);
}
}

and for this to work, TestMacroMethods needs to be declared as a regular
Groovy extension module. In that case, *any* code using "safe" would be
transformed, so:

safe(x.foo()).bar()

will be expanded *at compile time* into x.foo?x.foo().bar():null

A more interesting example with pattern matching can be found here:
https://github.com/bsideup/groovy-pattern-match/blob/master/src/main/java/ru/trylogic/groovy/pattern/PatternMatchingMacroMethods.java


An important thing to understand is that the @Macro annotation is *not* an
AST transformation. Instead, it's a marker annotation which is looked up
at compile time, for each method call. So for each method call of the AST,
we try to find if an extension method node of the same name exists, is
annotated with @Macro and takes MacroContext as the first argument and in
that case, the original method call is transformed thanks to the extension
method at compile time. The code of the macro itself is regular AST
transformation code. It does *not* use the macro stuff from the first
implementation, hence doesn't simplify writing xforms. On the other hand,
it greatly simplifies the availability of transforms by making them
writable as simple extension methods, without the need of ceremony
(annotations). It is actually very cool, but it comes at a price.
Performance wise, it is still a global AST transformation, but worse, for
each and every call, it implies finding an extension method node. It can
(and will) cost a lot, but we can improve the situation by introducing
specific caches. Moreover, it also implies that the sole fact of adding a
macro "jar" on classpath could potentially affect the semantics of your
program: if a method call in your code matches the name of a macro method
call, then it would be applied at compile time. Of course, one could argue
that it is already the case for extension modules, but the risk is lower:
for extension modules, the only cases of conflicts is when the receiver of
the message matches the class of the extension module. For @Macro, any
method call on "implicit this" would be matched. This draws the point of
whether the @Macro stuff should also be combined with a @UseMacro local
AST transformation, in order to avoid the problem of the global one.

If we do, then definitely, the performance issue is not one anymore,
because only marked code would be transformed. On the other hand, we
introduce ceremony again, which would mean that we loose the benefit of
extension methods being transparently visible. In the case of the pattern
matching stuff, this would mean that you would have to explicitly annotate
your code to enable the feature. Is it a problem? I'm not sure actually.
Also it's worth noting that the global AST transformation issue is less of
a problem if you have lots of macros on classpath, because a single
transformation would apply them all in a single pass.

Last but not least, my feeling is that what we need is something in
between the first macro implementation and the second one. In particular,
I would like to be able to write the @Macro method body using those macro
{ ... } blocks. It makes a lot of sense to me. Next, I wouldn't like to be
limited to expressions. I think a macro should be able to produce any AST
node. This implies expressions, but also statements or even full methods.

I gave an example a little contrived, I admit, to Sergei, which is,
imagine that I want to generate two methods with the same body, but
accepting two different argument types. Then I could write a macro that
generates the method:

@Macro MethodNode createMultiply(MacroContext ctx, ClassNode argType) {
macro {
_argType_ multByTwo(_argType_ x) { 2 }
}
}

Then in a class I could write:

class Calculator {
createMultiply int
createMultiply double
}

Of course, this wouldn't compile because the grammar wouldn't allow
defining a method in a closure (in the macro block), and it would not
recognize the createMultiply calls directly in the class body, but I kind
of like the idea of a macro system that just allows expanding and
reasoning at the AST level anywhere, because it lets us create new
language constructs.

In any case, let us know what you think, what you expect from a macro
system in Groovy. We have very good starting points, let's make it rock
solid :-)
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
--
Best regards,
Sergei Egorov
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/




NOTICE from Ab Initio: This email (including any attachments) may contain
information that is subject to confidentiality obligations or is legally
privileged, and sender does not waive confidentiality or privilege. If
received in error, please notify the sender, delete this email, and make
no further use, disclosure, or distribution.
Cédric Champeau
2014-10-20 17:57:48 UTC
Permalink
It's a bit soon to talk about limitations, but I expect problems with
generics, as well as the syntax to transform statements (it is easy to
transform an expression into another expression,but transforming a
statement is more complex).
Post by ngalarneau-a7zLCEouqeN+cjeuK/
Cedric,
That looks exciting!
What limitations do you foresee?
Neil
Date: 10/17/2014 12:43 PM
Subject: Re: [groovy-dev] State of the macros
------------------------------------------------------------------------
So here is the update for the end of the week. I worked with Sergei on
1. being able to generate classes with macros
2. providing an AST matcher
I think 2 is very important if we want to simplify AST xforms.
Basically, if you need an AST transform to trigger on a specific
condition (like matching a method call of a certain form), it can be
very complicated and require a lot of boilerplate code. So the idea
would be to provide AST matching, where you would describe what you
look for, and the matching engine would return the list of AST nodes
that match. The good news is that I have a prototype working, and you
_https://github.com/melix/groovy-core/blob/d42fd1b39c5c421daf6cacb45403f8dcb06f8f12/subprojects/groovy-macro/src/test/groovy/org/codehaus/groovy/macro/matcher/ASTMatcherTest.groovy#L35_
The API is far from being final, and as an example, I wrote an example
def ast = macro {
println((a*b)+(a*c))
}
use(ASTMatcher) {
def pattern = macro {
(a*b)+(a*c)
}
def replacement = macro {
a*(b+c)
}
def result = ast.replace(pattern).with(replacement)
def expected = macro {
println (a*(b+c))
}
assert result.matches(expected)
}
What do you think?
Last but not least, the more I work with macros, the less I feed the
need for the global AST transformation. I think annotating the code
that uses the "macro" block with a local AST xform would be enough.
Thoughts?
Great brief, Cedric!
I'm here and ready to discuss both features.
simple: I decided to extract code base from MacroGroovy, because
it can be used outside of "AST nodes generation" context.
* MEMs are implemented with Global AST transformation with method
call scanning, and it costs. But, there are already almost the
same transformation called AstBuilderTransformation. Most
backward compatibility. So, it's not about new global xform. Just
rethinking of old one.
On Tue, Oct 7, 2014 at 8:20 PM, Cédric Champeau
Hi everyone,
You may know that we have long been complaining about the complexity
of AST transformations in Groovy. There are a very powerful tool, but
it is still a bit complex to handle. In Groovy 2.4, we planned to
macros". Basically the idea is to have a simple way to replace classic
method class into more complex expressions. In this email, I'll try to
summarize the project and give some ideas about the orientations we
want for the Groovy language. In the end, your opinion matters so
please feel free to comment.
First of all, Sergei actually worked on two "macro" projects. The
*def *someVariable = *new *VariableExpression(*"someVariable"*);
ReturnStatement result = macro {
*return new *NonExistingClass($v{someVariable});
}
As you can see, it is some kind of "super AstBuilder" which is capable
of handling variables/expressions from an external context thanks to
proper escaping with $v. This macro code is primarily aimed at being
used inside AST transformation themselves, and dramatically reduce the
amount of code required to generate an AST tree. An advantage of those
macro blocks is that if you use them in AST transformations, you're
actually not limited to expressions. You can generate anything, as
long as what is inside the macro { ... } block is supported by the
Groovy syntax. A drawback of the current implementation is that it
uses a global AST transformation. So as soon as you have the macro
project on classpath, every single method call corresponding to
"macro" will be interpreted as a macro block. There are multiple
disadvantages of global AST transformations. First of all, they are,
as the name says, global, meaning that they apply independently on
*every* class being compiled by Groovy. This also means that the
transformation is run even if you know the code you write is not using
it. In particular, we need to inspect the full AST, in-depth, to find
potential method calls named "macro" even if there's not a single one
in the code (because you will only know once you have visited the full
AST). In short, a global AST transformation has a clear performance
impact, because it is executed independently of the context. For the
macro stuff, an easy workaround would be to transform the global AST
transformation into a local one. For example, an AST transformation
using the macro system could declare it by annotating with
@EnableMacros. I think it is reasonable, since it is very unlikely
that you would use the macro { ... } stuff in regular code. The macro
block is indeed useful, but in the end, it is limited to writing other
AST transformations that would be either global or local. In short,
the current macro { ... } block is useful for AST transformation
designers, but cannot be used by "regular" Groovy users to define new
language constructs.
In answer to that problem, Sergei worked on a second implementation of
you can define extension methods in Groovy (see
_http://docs.groovy-lang.org/2.3.7/html/documentation/#_extension_modules_),
*public class *TestMacroMethods {
@Macro
*public static *Expression safe(MacroContext macroContext,
MethodCallExpression callExpression) {
*return */ternaryX/(
/notNullX/(callExpression.getObjectExpression()),
callExpression,
/constX/(*null*)
);
}
}
and for this to work, TestMacroMethods needs to be declared as a
regular Groovy extension module. In that case, *any* code using "safe"
safe(x.foo()).bar()
will be expanded *at compile time* into x.foo?x.foo().bar():null
_https://github.com/bsideup/groovy-pattern-match/blob/master/src/main/java/ru/trylogic/groovy/pattern/PatternMatchingMacroMethods.java_
*not* an AST transformation. Instead, it's a marker annotation which
is looked up at compile time, for each method call. So for each method
call of the AST, we try to find if an extension method node of the
the first argument and in that case, the original method call is
transformed thanks to the extension method at compile time. The code
of the macro itself is regular AST transformation code. It does *not*
use the macro stuff from the first implementation, hence doesn't
simplify writing xforms. On the other hand, it greatly simplifies the
availability of transforms by making them writable as simple extension
methods, without the need of ceremony (annotations). It is actually
very cool, but it comes at a price. Performance wise, it is still a
global AST transformation, but worse, for each and every call, it
implies finding an extension method node. It can (and will) cost a
lot, but we can improve the situation by introducing specific caches.
Moreover, it also implies that the sole fact of adding a macro "jar"
if a method call in your code matches the name of a macro method call,
then it would be applied at compile time. Of course, one could argue
that it is already the case for extension modules, but the risk is
lower: for extension modules, the only cases of conflicts is when the
receiver of the message matches the class of the extension module. For
@Macro, any method call on "implicit this" would be matched. This
problem of the global one.
If we do, then definitely, the performance issue is not one anymore,
because only marked code would be transformed. On the other hand, we
introduce ceremony again, which would mean that we loose the benefit
of extension methods being transparently visible. In the case of the
pattern matching stuff, this would mean that you would have to
explicitly annotate your code to enable the feature. Is it a problem?
I'm not sure actually. Also it's worth noting that the global AST
transformation issue is less of a problem if you have lots of macros
on classpath, because a single transformation would apply them all in
a single pass.
Last but not least, my feeling is that what we need is something in
between the first macro implementation and the second one. In
using those macro { ... } blocks. It makes a lot of sense to me. Next,
I wouldn't like to be limited to expressions. I think a macro should
be able to produce any AST node. This implies expressions, but also
statements or even full methods.
I gave an example a little contrived, I admit, to Sergei, which is,
imagine that I want to generate two methods with the same body, but
accepting two different argument types. Then I could write a macro
@Macro MethodNode createMultiply(MacroContext ctx, ClassNode argType) {
macro {
_argType_ multByTwo(_argType_ x) { 2 }
}
}
class Calculator {
createMultiply int
createMultiply double
}
Of course, this wouldn't compile because the grammar wouldn't allow
defining a method in a closure (in the macro block), and it would not
recognize the createMultiply calls directly in the class body, but I
kind of like the idea of a macro system that just allows expanding and
reasoning at the AST level anywhere, because it lets us create new
language constructs.
In any case, let us know what you think, what you expect from a macro
system in Groovy. We have very good starting points, let's make it
rock solid :-)
--
Cédric Champeau
SpringSource - Pivotal
_http://twitter.com/CedricChampeau_
_http://melix.github.io/blog_
_http://spring.io/__http://www.gopivotal.com/_
--
Best regards,
Sergei Egorov
--
Cédric Champeau
SpringSource - Pivotal
_http://twitter.com/CedricChampeau_
_http://melix.github.io/blog_
_http://spring.io/__http://www.gopivotal.com/_
NOTICE /from Ab Initio: This email (including any attachments) may
contain information that is subject to confidentiality obligations or
is legally privileged, and sender does not waive confidentiality or
privilege. If received in error, please notify the sender, delete this
email, and make no further use, disclosure, or distribution. /
--
Cédric Champeau
SpringSource - Pivotal
http://twitter.com/CedricChampeau
http://melix.github.io/blog
http://spring.io/ http://www.gopivotal.com/
marcin.grzejszczak
2014-11-14 10:09:14 UTC
Permalink
That looks nice!

I've been looking at the code and it would really nice if you could briefly
explain those keywords in the withConstraint section.

* placeholder
* anyToken
* eventually

BR,
Marcin



--
View this message in context: http://groovy.329449.n5.nabble.com/State-of-the-macros-tp5721429p5721693.html
Sent from the groovy - dev mailing list archive at Nabble.com.

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

http://xircles.codehaus.org/manage_email
Cédric Champeau
2014-11-14 14:03:22 UTC
Permalink
Hi,

Sure, let's start with placeholder. Imagine you have the following tree:

foo()+bar()

And that your pattern is : a+b

Normally, the pattern will *not* match the tree because foo() is not
strictly 'a', and bar() is not strictly 'b'. For that, you need to declare
both 'a' and 'b' as placeholders. In that case it means that you will match
any binary expression which operator is "+".

Say that you only declare 'a' as a placeholder. Then:

foo()+bar()

would not match (because 'b' is not a placeholder), but:

foo()+b

would. Now imagine that the pattern is:

a+a

and that you declare 'a' as a placeholder. Then the matcher will require
that both operands are equivalent, so:

foo()+bar()

will not match, but:

foo()+foo()

will.

Let's keep our pattern a+a with 'a' as a placeholder and now imagine that
the tree is:

foo()-foo()

Then again, it will *not* match because the pattern strictly required a
'+'. By declaring "anyToken()" in the constraints block, then you relax
matching on the operator, and it will match on *any* operator. Instead of
using "anyToken", you can also use a custom block that says if the operator
is accepted or not.

As for the "eventually" block, let's illustrate it with the following
example. First, this tree:

(a+b)-(a+b)

and the following pattern:

(a+b)+(a+b)

You can make the pattern match on the tree by using "anyToken". But imagine
that you also want the (a+b) expressions to match on any pattern too. Then
you would add "anyToken" on each too. But now, imagine that you want to
match *only* if you have the same operator on both sides:

(a-b)+(a-b)

would need to match, but not:

(a-b)+(a+b)

because the operator of the left expression is not the same as the operator
of the right expression. This can be expressed using the eventually block
on the top level pattern, by saying "eventually, I want both left and right
binary expressions to have the same operator".

Does it make sense?
Post by marcin.grzejszczak
That looks nice!
I've been looking at the code and it would really nice if you could briefly
explain those keywords in the withConstraint section.
* placeholder
* anyToken
* eventually
BR,
Marcin
--
http://groovy.329449.n5.nabble.com/State-of-the-macros-tp5721429p5721693.html
Sent from the groovy - dev mailing list archive at Nabble.com.
---------------------------------------------------------------------
http://xircles.codehaus.org/manage_email
marcin.grzejszczak
2014-11-15 12:45:45 UTC
Permalink
Yeah - it does.

I'll try to take a look at that further on and keep you posted with any of
my findings.



--
View this message in context: http://groovy.329449.n5.nabble.com/State-of-the-macros-tp5721429p5721702.html
Sent from the groovy - dev mailing list archive at Nabble.com.

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

http://xircles.codehaus.org/manage_email

Loading...