-
Notifications
You must be signed in to change notification settings - Fork 213
Functional/Expression macro #1874
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I'm not sure what you mean by "but not what is before and after this code". In all of your examples, it appears that the macro is not just changing the expression @macro(params);
final value; ? |
I meant that with: print(a);
var foo = @macro(...);
print(b) then the macro can only impact
That would make sense too. I don't have a strong attachment to this exact syntax Although I'm not sure how type-inference would work in this case. I think it'd be valuable to allow: Widget build(context) {
return Text(@ValueListenableMacro(listenable));
} which would generate: Widget build(context) {
return ValueListenableBuilder<String>(
valueListenable: listenable,
builder: (context, value, _) => Text(value),
);
} Same thing for Although I could see this be complex to support. I'd be fine with only allowing: @macro(...)
final value |
A couple things: If the thing behind the @macro(...)
final value; To clarify, I think what is being asked is expression macros that are able to replace the statement that they are a part of (not just the expression). The macro takes in an expression, but actually produces multiple statements including for example local getter / setters, which then get further transformed in some cases into class fields / getters / setters or in some case classes. This is at odds with the current macro proposal in which expression macros (if accepted) would be run during a 4th phase that would 1. not be run before the @functionalWidget annotation 2. Not be able to introduce new types. However, I think that they have interesting use cases that should be thought about. Personally I think allowing annotations to access a some type of AST (could be serialized) for the annotated declaration would be less invasive then some of these solutions that run up against the limitations that expression macros are likely to have. As of #1861, Code objects are being considered for macro parameters, and I believe a similar interface for this sort of info could be useful in several of the situations that @rrousselGit mentions. So the minimal set of things I see that are required to enable these use cases while still being more inline with the current macro proposal are:
With access to the AST I don't think you would actually need local getters / setters (which would not really be a language feature needed if the intermediate generated getter / setter were omitted or removed from the final generated code). |
@rrousselGit, here are some observations: As you noted in your
doesn't exactly hold. Note that your @state(0)
external count; generates int get count => state.count;
set count(int value) => state.count++; Your Overall, the notion of an |
That is not quite true, as This is similar to how macros on function may allow adding try/catch, such that: @action
void fn() {
print('foo');
} becomes: @action
void fn() {
try {
print('foo');
} catch (...) {...}
} The original content of the function wasn't modified, but simply wrapped. |
Another use-case could be implementing a cancellable async/await Like: class Example {
@cancelable
CancelableOperation<int> fn() {
Future<T> future;
T value = @cancellableAwait(future);
return 42;
});
} |
this would open up so much potential to create frameworks |
There have been a few discussions related to this (such as jakemac53/macro_prototype#29), but I don't think there's a problem language issue for it yet, so here it is
Long story short, the idea for this proposal is to allow metaprogramming to define macros that are applied on expressions, such users can write:
And the macros would be able to replace the
final value = @macro(params);
code (but not what is before and after this code) with a modified expression.This could enable a variety of useful patterns, such as:
Implementing an await-like keyword for Widget builders
A common issue with Flutter widgets is that they tend to get rapidly nested:
This is an issue similar to
Future.then
, which the language solves withawait
, but Widgets have no equivalent.With expression macros, we could instead write:
and the macros would desugar this code into the previous code
Stateful functional widgets
Through expression macros, it could be possible to drastically simplify stateful widgets by offering a
@state
macro, typically combined with a@functionalWidget
macro, such that we'd define:And this would desugar
var count = @state(0)
into:such that full final code would be:
(this assumes that we can define local gettters/setters, but I think that's a reasonable request)
Guards
A common thing when dealing with
null
or functional classes likeResult
/Either
is to do:or:
...
This could be simplified by a
@guard
macro, such that we would define:and this would generate the
if (x == null) return null
/if (x.isError) return Result<T>.errorFrom(x)
for us.Custom compiler optimisations
Rather than simplifying existing code, an alternate use-case would be to make existing code more performant.
A use-case I personally have is with Riverpod, where users can do:
where
ref.watch
works by doing aMap.putIfAbsent
, such that the previousref.watch
usages are roughly equal to:This works fine, but it would be better if we could remove this
Map
. With expression macros, we could possibly implement a@watch
, such that we'd have:and this would equal:
effectively removing the
Map
in the process by exploding it into class propertiesDependency tracking without AST parsing
Using the same code as the previous example, Riverpod would like to generate a static list of dependencies associated with a "provider".
The idea is that with:
we would like macros to generate the
dependencies
parameter ofexample
. The problem being, existing macro proposals currently do not allow macros to access the AST. Which means something like:would not be able to search for
ref.watch
invocations to generate thatdependencies
list.But if we replaced
ref.watch
with a@watch
macro, we could possibly have that@watch
do the generation, such that individual@watch
usage would all declare their parameter in thedependencies
listNote:
I am aware that this use-case would likely require subtle changes to the
Provider
usage to work. Because macros likely wouldn't be able to push items into a collection. The important is being able to statically extra calls toref.watch
within the function. From there, theProvider
definition could change to match the requirements.The text was updated successfully, but these errors were encountered: