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

Conversation

Projects
None yet
6 participants
@Simn
Copy link
Member

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

This comment has been minimized.

Copy link
Member

RealyUniqueName commented Sep 23, 2018

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

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

This comment has been minimized.

Copy link
Member

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

This comment has been minimized.

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

2 checks passed

continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details

@Simn Simn deleted the Simn:meta_using branch Sep 25, 2018

@skial skial referenced this pull request Sep 26, 2018

Closed

Haxe Roundup 449 #546

1 of 1 task complete
@remcohuijser

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Copy link

nanjizal commented Nov 30, 2018

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

This comment has been minimized.

Copy link
Member

back2dos commented Nov 30, 2018

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

This comment has been minimized.

Copy link

remcohuijser commented Nov 30, 2018

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

This comment has been minimized.

Copy link

remcohuijser commented Nov 30, 2018

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