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

Add @:using #7462

Merged
merged 5 commits into from
Sep 25, 2018
Merged

Add @:using #7462

merged 5 commits into from
Sep 25, 2018

Conversation

Simn
Copy link
Member

@Simn Simn commented Sep 23, 2018

As discussed, this allows type-level static extensions through the @:using(Path1, Path2) metadata. It works exactly like module-level static extensions and is checked right after that, but before global static extensions.

I was considering suggesting a specific syntax for this, e.g. class Main using MainUsing { }, but I'm not sure if that's necessary.

There are no extensive tests yet; this is my usage example:

// MainMacroUsing.hx
import haxe.macro.Expr;

class MainMacroUsing {
	macro static public function getVersion(e:ExprOf<Main>) {
		trace("Getting version...");
		return macro 9001;
	}
}
// Main.hx
@:using(Main.MainUsing, MainMacroUsing)
class Main {
	static public function main() {
		var m = new Main();
		m.useMain("yay");
		trace(m.getVersion());
	}

	function new() { }
}

class MainUsing {
	static public function useMain(m:Main, arg:String) {
		trace('Main $m is being used with argument $arg');
	}
}

Output:

source/MainMacroUsing.hx:5: Getting version...
source/Main.hx:14: Main Main is being used with argument yay
source/Main.hx:6: 9001

And here's the obligatory example with enums so everybody goes "Aaaah so that's why this is useful":

// MyOption.hx
@:using(MyOption.MyOptionTools)
enum MyOption<T> {
	None;
	Some(v:T);
}

class MyOptionTools {
	static public inline function get<T>(o:MyOption<T>) {
		return switch (o) {
			case None: throw false;
			case Some(v): v;
		}
	}
}
// Main.hx
import MyOption;

class Main {
	static public function main() {
		var myOption = Some(12);
		trace(myOption.get());
	}
}

Generated JS:

Main.main = function() {
	console.log("source/Main.hx:6:",12);
};

I even remembered to show these fields in completion.

@RealyUniqueName
Copy link
Member

Is it possible to implement it via keyword instead of a meta?

enum MyOption<T> using MyOption.MyOptionTools {...}

@Simn
Copy link
Member Author

Simn commented Sep 23, 2018

As I said, I'm not sure if that's necessary. Also, the nice thing about metadata is that you can inject them from the outside...

@kevinresol
Copy link
Contributor

kevinresol commented Sep 23, 2018

I think the using keyword can be introduced on next minor version (4.1) so that we can properly guard it with #if haxe_ver >= 4.1 (given we don't have 4.1.0-rc.1 that last for months). Currently we can't because we can't distinguish haxe 4 and the preview versions. So the metadata @:using being backward compatible (simply ignored) is a better choice for now.

@Simn Simn merged commit fa49aff into HaxeFoundation:development Sep 25, 2018
@Simn Simn deleted the meta_using branch September 25, 2018 07:42
@skial skial mentioned this pull request Sep 26, 2018
1 task
@remcohuijser
Copy link

remcohuijser commented Nov 30, 2018

Sounds like a pretty cool approach and so I tried using this on a Vector3 typedef without much success.

typedef Vector3 = 
{
    var X : Float;
    var Y : Float;
    var Z : Float;
}

The reason for using typedefs here is that the compiler is often able to optimize the code, resulting in not creating an instance at all (which is very nice!). For example:

var temp : Vector3 = {X: 0, Y: 0, Z: 0};

Becomes:

var inlobj_X = 0;
var inlobj_Y = 0;
var inlobj_Z = 0;

The regular way of adding, for example, an add function to Vector3 with static extension and the using keyword fine. I really liked the approach in this topic because it allows the Vector3 typedef to kind of define that it always wants a set of functions.

@:using(Vector3.Vector3Math)
typedef Vector3 = 
{
    var X : Float;
    var Y : Float;
    var Z : Float;
}

class Vector3Math
{
    public static inline function Add(vector : Vector3, value : Float)
    {
		return
        {
            X: vector.X + value,
            Y: vector.Y + value,
            Z: vector.Z + value
        } 
	}
}
import project.Vector3;

class Test
{
    public function new()
    {
        var vector : Vector3 = {X: 0, Y: 0, Z: 0};
        temp.Add(10);
    }
}

The result is that the compiler outputs that project.Vector3 has no field Add.

@remcohuijser
Copy link

remcohuijser commented Nov 30, 2018

Another "closely" related thing is that it would be very cool if one could create static constructors through this approach as well. So for example:

@:using(Vector3.Vector3Math)
typedef Vector3 = 
{
    var X : Float;
    var Y : Float;
    var Z : Float;
}

class Vector3Math
{
    public static inline function Empty(vector : Vector3, value : Float)
    {
	return
        {
            X: 0,
            Y: 0,
            Z: 0
        } 
    }
}

And then use it like this:

import project.Vector3;

class Test
{
    public function new()
    {
        var vector : Vector3.Empty();
    }
}

@nanjizal
Copy link
Contributor

remcohuijser
Did you see if wrapping the typedef in an abstract allows using to work in the way you would like for your first example.

@back2dos
Copy link
Member

The problem is mostly that Vector3 is not a value and even if it were a class, then Vector3 is not of type Vector3 so the static extensions won't apply.

FWIW this gives you the same interface (although it differs from a type perspective):

@:structInit class Vector3 {
  var X : Float;
  var Y : Float;
  var Z : Float;
  static public function Empty():Vector3 return { X: 0, Y: 0, Z: 0 };
}

@remcohuijser
Copy link

Did you see if wrapping the typedef in an abstract allows using to work in the way you would like for your first example.

Well this indeed gets me a bit further, thanks for that!

@remcohuijser
Copy link

The problem is mostly that Vector3 is not a value and even if it were a class, then Vector3 is not of type Vector3 so the static extensions won't apply.

FWIW this gives you the same interface (although it differs from a type perspective):

@:structInit class Vector3 {
  var X : Float;
  var Y : Float;
  var Z : Float;
  static public function Empty():Vector3 return { X: 0, Y: 0, Z: 0 };
}

Thanks for thinking with me here. I really want to make them as low level as possible so that the compiler can optimize most of the stuff away. I actually think that I might look into writing a macro that converts occurrences of a vector3 inside a function to 3 separate float values.

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

Successfully merging this pull request may close these issues.

6 participants