Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

macro new - was: Configurable @:structInit initializer #5292

Open
vizanto opened this issue May 29, 2016 · 19 comments
Open

macro new - was: Configurable @:structInit initializer #5292

vizanto opened this issue May 29, 2016 · 19 comments
Assignees
Milestone

Comments

@vizanto
Copy link

vizanto commented May 29, 2016

@Simn, you know what I'm talking about. ;D

As of Haxe 3.3.0-rc1, classes annotated with @:structInit will have their constructor called with arguments from the struct literal expression.

I'm proposing a small addition to this mechanism to allow a macro to modify this expression. Instead of always mapping it to a constructor call, pass it to a macro call. By allowing constructors to be macros:

@:structInit
class Point {
  // one Expr per constructor argument?
  // array of Expr?
  // EDIT:  we have inline new,  why not macro new?
  public macro function new(e : Expr) {
    // ...
  }

  var x : Int;
  var y : Int;
}

This would allow us to implement several things in libraries:

  • Object pooling
  • Immutable objects with statically known instance memoization
    • (replace var p : Point = {}; with var p = Point.empty;)
  • Type conversion: var p : Point = {x: "1", y: "2"}; => macro calling Std.parseInt

Because @:structInit would reverse the macro call "responsibility", this gives us new macro powers without having to abuse @:build or similar at the instantiation point.


If the compiler would pass array literals to macro structInit as well, this would allow implementing set literals using array syntax:

var s : Set = [1, 2, 3];
s.contains(1) // => true
@Simn Simn self-assigned this May 31, 2016
@Simn Simn added this to the 3.4 milestone May 31, 2016
@vizanto
Copy link
Author

vizanto commented Nov 23, 2016

Can I bump this, dear @Simn 😄

@ncannasse
Copy link
Member

to be conformant with @:build, maybe it should be `@:structInit(my.MacroClass.doSomething()) which we would append the object as last argument, but then that's no longer a structInit thing since you can return any AST that would not create a Point, so maybe the semantic is more about

@:new(my.MacroClass.onNew())

And this would pass either arguments of "new" or object initializer if structInit

@vizanto
Copy link
Author

vizanto commented Nov 23, 2016

I like it. But for expression to be typed the macro should return something that's typable as Point (in the example), right?

var p : Point = 123.123; // type has to become a Point

So the onNew macro should return Expr<Point> ...otherwise it's a pretty pointless macro.

How about keeping @:structInit and adding public macro function new(expr : Expr)?


I have no strong opinion on syntax. Symmetry with @:build is nice, but there's also inline new which feels related.

@Simn Simn modified the milestones: 3.4, 4.0 Jan 9, 2017
@vizanto
Copy link
Author

vizanto commented Sep 14, 2017

Bump after awesome "eval" talk :D

@vizanto
Copy link
Author

vizanto commented Sep 16, 2017

Simon said he likes the idea of macro function new(...args) in addition to inline and regular constructors. But this was after a few beers so who knows...

@Simn
Copy link
Member

Simn commented Sep 17, 2017

I can't think of a technical reason not to support macro new. Seems quite straightforward to me in fact.

@piotrpawelczyk
Copy link

Isn't it similar to what can be achieved with @:from static macro function in abstracts?

@vizanto
Copy link
Author

vizanto commented Sep 17, 2017

@szczepanpp not really unless @:structInit is supported on abstracts?

@piotrpawelczyk
Copy link

piotrpawelczyk commented Sep 17, 2017

@vizanto maybe I'm not noticing something crucial about this proposal but as I understand it you get exactly the same usage with abstracts right now. I'm not saying I'm against, just pointing out that we have this feature already. Btw. you show example of a Set type - you'd have to use abstract on collection classes anyway just for an @:arrayAccess (in a particular case of a Set it may be a bit unusual but it kind of looks nice to me: if(mySet[myVal]) expr; but maybe @:arrayAccess is more suitable for an OrderedSet). :)

It's greatly simplified, just to show initialization from an Array (http://try-haxe.mrcdk.com/#Db081):

import haxe.macro.Expr;

class Test {
  static function main() {
    trace(([1, 2, 3]: Set<Int>)); // 1,2,3
    var mySet: Set<Int> = [1, 2, 3];
    trace(mySet); // 1,2,3
  }
}

abstract Set<T>(Array<T>) {
  public function new(arr: Array<T>)
  	this = arr;
  
  @:from
  public static macro function fromMacro<T>(expr: ExprOf<Array<T>>): ExprOf<Set<T>> {
    trace("Goes through a macro!");
    return macro new Set($expr);
  }
  
  // Currently there's something wrong when using macro @:from function
  @:from
  public static function fromAny<T>(any: Dynamic): Set<T> { // where's `Any` ?!
    throw "Shouldn't be called!";
  }
}

@vizanto
Copy link
Author

vizanto commented Sep 17, 2017

@szczepanpp thanks, you're right that @:structInit probably doesn't have to allow array syntax.

My latest proposal (which may not be clear when reading from top to bottom) is to allow macro new in addition to inline new or regular new.

This allows classes tagged with @:structInit to do more than the default behaviour, without the complexity of additional abstract type wrappers.

@vizanto
Copy link
Author

vizanto commented Sep 17, 2017

Hmm, @szczepanpp if I change your example slightly: trace("Goes through a macro! " + expr); it traces expr as {pos: #pos(Test.hx:5: characters 12-21),expr: EConst(CIdent(this))}

I can't find the [1, 2, 3] values in there.

@piotrpawelczyk
Copy link

That's a thing I wonder about myself. Didn't need to do anything with it yet, so I wasn't really exploring what it means and how to actually use it. I guess our macro expert knows the answer (@back2dos of course). :)

@Simn Simn modified the milestones: Release 4.0, Design Apr 17, 2018
@vizanto
Copy link
Author

vizanto commented Jul 15, 2018

@Simn please don't forget macro function new for Haxe 4 😄

@vizanto vizanto changed the title Configurable @:structInit initializer macro new - was: Configurable @:structInit initializer Jul 15, 2018
@Simn Simn modified the milestones: Design, Release 4.0 Sep 18, 2018
@Simn
Copy link
Member

Simn commented Sep 18, 2018

I would like to add macro new for Haxe 4. I don't think we need a haxe-evolution for this.

@ncannasse
Copy link
Member

Is it macro new or @:new(my.Macro.method) ? the first is better in syntax but then requires a lot of #if macro / #if !macro in the class file

@back2dos
Copy link
Member

back2dos commented Sep 19, 2018

You're right, although I would speculate that a class that has a macro constructor is also quite likely to have macro methods, in which case it's going to be an #if !macro-fest anyway.

@Simn Simn modified the milestones: Release 4.0, Release 4.1 Oct 9, 2018
@Simn
Copy link
Member

Simn commented Oct 9, 2018

Pushing this back to 4.1 because I'm concerned about conditional compilation problems. It's also not a breaking change.

@vizanto
Copy link
Author

vizanto commented Sep 20, 2019

4.1 will be epic, someday

@kLabz
Copy link
Contributor

kLabz commented Sep 20, 2019

Can't wait to be in 2022

@Simn Simn modified the milestones: Release 4.1, Release 4.2 Feb 19, 2020
@Simn Simn modified the milestones: Release 4.3, Later Mar 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants