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

Property specifier which allows writing only from constructor #3442

Closed
Simn opened this issue Oct 5, 2014 · 21 comments
Closed

Property specifier which allows writing only from constructor #3442

Simn opened this issue Oct 5, 2014 · 21 comments
Milestone

Comments

@Simn
Copy link
Member

Simn commented Oct 5, 2014

This was brought up and rejected before, but this time I have a better understanding of the benefits and would like to present it again. The idea is to have a property specifier such as new which allows writing a field from a class constructor but nowhere else.

What's nice about this is that we can infer field immutability from it, which allows us to e.g. propagate constants even if an object leaves context. Consider this piece of code:

class MyVector {
    public var x(default, null):Float;
    public var y(default, null):Float;

    public inline function new(x:Float, y:Float) {
        this.x = x;
        this.y = y;
    }
}

class Main {
    static function main() {
        var vec = new MyVector(12, 13);
        leave(vec);
        use(vec.x, vec.y);
    }

    static function leave(vec:MyVector) { }
    static inline function use(x:Float, y:Float) {
        return x + y;
    }
}

The call to leave(vec) cancels constructor inlining. Now if both x and y were defined to be writable-only-from-constructor, we could still use their values and optimize the use(vec.x, vec.y) call to plain 25.

@nadako
Copy link
Member

nadako commented Oct 5, 2014

I don't think that new is a good modifier for those properties. I guess it's better to re-use @:final metadata (or maybe @:readOnly but that is only used in C# currently)

@Simn
Copy link
Member Author

Simn commented Oct 5, 2014

That doesn't make any sense. It would introduce e.g. @:readonly var x(get, set):String which does nothing but raise questions. It's much better to have a special accessor identifier because this is not supposed to be orthogonal to existing modifiers.

@nadako
Copy link
Member

nadako commented Oct 5, 2014

ah, you mean like var x(new,new):Int?

@waneck
Copy link
Member

waneck commented Oct 5, 2014

I think he meant something like var x(default,new):Int
Btw, I'm really in favour of getting this into the language

@nadako
Copy link
Member

nadako commented Oct 5, 2014

Yeah I misunderstood the meaning of property specifier, so ignore me :-)

@ncannasse
Copy link
Member

I don't understand the example : if we have a call to leave, we cannot inline the constructor unless leave() is inlined as well, since we need to pass it a MyVector object.

The thing we might want instead is this:

@:struct class MyPoint { .... }

In a struct classes, we enforce that member variables can only be written in constructor, and we always substitute the value by the list of member vars, which means that function anything( p : MyPoint ) becomes function anything( p_x :Float, p_y : Float ). This change the pass-by-reference to pass-by-value but since member vars are immutable, we're fine.

Another thing:

class A {
    public var p : MyPoint;
}

Turns into:

class A {
    public var p_x : Float;
    public var p_y : Float;
}

The only place when MyPoint is used as an object is when unified with a type parameter (Array of MyPoint for instance), of with a structure such as { x : Float, y : Float }

@ncannasse
Copy link
Member

PS : this is what C++ does when you don't use pointers

@ncannasse
Copy link
Member

PPS : struct classes cannot be extended (and cannot extend a non-struct class ?)

@Simn
Copy link
Member Author

Simn commented Oct 6, 2014

I don't want to inline the constructor, I want to use the field values for constant propagation. This is quite easy to implement for immutable fields because we do not have to worry about objects leaving context, or their aliases leaving context.

@Simn
Copy link
Member Author

Simn commented Oct 6, 2014

Here's a small demo commit: Simn@e539939

This is all that's needed to allow this optimization: https://gist.github.com/Simn/484866c918d6abcd5f9e

Obviously this optimization it not safe for arrays and structures because the fields could be written to, it's just to get the idea across and because structure and array is easier to implement than constructor (which has to look at the field code).

So what I really want here is two-fold:

  1. A way to declare instance fields immutable.
  2. A way to declare an array or structure declaration "const", similar to what @nadako's Const abstract does.

@Atry
Copy link
Contributor

Atry commented Oct 8, 2014

I think we could simply allow write access from a constructor to a var field(default, never).

@Atry
Copy link
Contributor

Atry commented Oct 8, 2014

We already allowed it for structure types.

var o:{ var foo(default, never):Int; } = { foo: 1 };

@Atry
Copy link
Contributor

Atry commented Oct 8, 2014

I like @:struct class, but I don't know why we still need abstract or inline function new if we have @:struct class.

@ncannasse
Copy link
Member

@Atry abstracts allow much more, such as auto cast and operator overloading. You could still have abstracts using struct class implementation for multi-field support

@Simn
Copy link
Member Author

Simn commented Oct 8, 2014

The @:struct class idea is quite interesting, but is going to require a lot more work than the simple "we can propagate this field's value because it's immutable" I want to go for first.

@vizanto
Copy link

vizanto commented Oct 10, 2014

@Simn +1
I would use this in my implementation of ValueObjects, where I do need objects to stay reference-able. (can't use struct class)

@dogles
Copy link
Contributor

dogles commented Dec 31, 2014

Cross-post from the google group:

Regarding struct classes, I would love the following features:

  1. Value types that are assigned by memberwise copy, and passed by-value to functions.
  2. Use language-native value types where available, otherwise emulate them.
  3. Arrays/Vectors of value types are stored linearly in memory.
  4. You can pass value types by-reference, e.g. through a Ref abstract.
  5. Support "boxing" of value types via Dynamic, similar to how C# boxing works.
  6. Bonus: support destructors on by-value types, in order to support RAII.
  7. Bonus: allow (de-)allocation and storage of references to by-value types for cases where you desire explicit memory management, via a runtime-managed heap.

@ncannasse
Copy link
Member

Hi Dan
3) is not really easy to do : how would you do that in JS for instance ?
4) feasible but very efficient if no native Ref support (will require to allocate a reference, copy the members from local to reference, then copy them back to locals afterwards). Also, for languages that supports native references, we need to forbid them from being stored in member variables since the value might not exists after we return from the function
5) yes, I guess that will be necessary, will require some alloc/copy similar to (4)
6+7) could maybe be done with abstracts if we add support for a destructor method (for (7) you could have your abstracts do the heap management)

@Atry
Copy link
Contributor

Atry commented Jan 4, 2015

I think implementing an explicit type ByValue<T> would be easier than @:struct class.

package haxe;
@:coreType abstrace ByValue<T>(T) from T to T
{
  @:to public function pack():T;
  @:from public static function unpack<T>(reference:T):ByValue<T>;
}
// Usage

class Sample
{

  public static function main()
  {
    // Explicitly pack and unpack
    var c = new MyClass(100, 50);
    var u = haxe.ByValue.unpack(c);
    var p = u.pack();

    // Implicitly unpack
    var v:ByValue<MyClass> = new MyClass(200, 100);

    trace(v.sub());
    trace(v.optimizedAdd());
  }

  static function optimizedDiv(v:ByValue<MyClass>)
  {
    return v.i / v.j;
  }

}

class MyClass
{
  public var i:Int;
  public var j:Int;

  public function inline new(i:Int, j:Int)
  {
    this.i = i;
    this.j = j;
  }

  public function sub():Int
  {
    $type(this); // MyClass
    return i - j;
  }

  @:thisByValue public function optimizedAdd():Int
  {
    $type(this); // ByValue<MyClass>
    return i + j;
  }

}

Will compile to:

class Sample
{

  public static function main()
  {
    // Explicitly pack and unpack
    var c = new MyClass(100, 50);
    var u_i:Int = c.i;
    var u_j:Int = c.j;
    var p:MyClass = {
      var temp:MyClass = new MyClass();
      temp.i = u_i;
      temp.j = u_j;
      temp;
    }

    // Implicitly unpack
    var v_i:Int = 200;
    var v_j:Int = 100;
    trace({
      var temp:MyClass = new MyClass();
      temp.i = v_i;
      temp.j = v_j;
      temp;
    }.sub())
    trace(MyClass.__impl_optimizedAdd(v_i, v_j));
  }

  static function optimizedDiv(v_i:Int, v_j:Int)
  {
    return v_i / v_j;
  }

}

class MyClass
{
  public var i:Int;
  public var j:Int;

  public function inline new(i:Int, j:Int)
  {
    this.i = i;
    this.j = j;
  }

  public function sub():Int
  {
    return i - j;
  }

  // A bridge method created by the Haxe compiler, which keeps the binary compatibility to the native platform
  public function optimizedAdd():Int
  {
    return __impl_optimizedAdd(this.i, this.j);
  }

  static function __impl_optimizedAdd(this_i:Int, this_j:Int):Int
  {
    return this_i + this_j;
  }

}

@Atry
Copy link
Contributor

Atry commented Jan 4, 2015

Note:

    trace(v.sub());

Becomes

    trace({
      var temp:MyClass = new MyClass();
      temp.i = v_i;
      temp.j = v_j;
      temp;
    }.sub())

When invoking a ByValue<T>'s instance method, a temporary instance T will be created, except the method is an inline method or a @:thisByValue method.

@Simn Simn removed this from the 3.4 milestone Jan 9, 2017
@Simn
Copy link
Member Author

Simn commented Sep 22, 2017

We have final now.

@Simn Simn closed this as completed Sep 22, 2017
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