HaxeFoundation / haxe-evolution

Repository for maintaining proposal for changes to the Haxe programming language
111 stars 58 forks source link

New syntax for getters & setters inspired by the C# property syntax #96

Open GasInfinity opened 2 years ago

GasInfinity commented 2 years ago

This would allow writing a property without creating functions for each getter & setter.

@:isVar
public var property:Int { get -> property; set -> property = value; }

Rendered Version

back2dos commented 2 years ago

I like it, mostly. It does miss the default case, although frankly I've always wondered if that was really such a good idea start with.

Still, having different syntax for the same thing tends to create confusion. I think moving forward with this should eventually lead to the deprecation and then removal of the current syntax. So we better be sure that all the cases we want to handle are handled (e.g. private set).

GasInfinity commented 2 years ago

Maybe for the default case, we could check for the final modifier in the getters and setters. With this, if the user doesn't provide a getter or a setter function (an Autoproperty), it should behave like a field access. Also, that getter or setter cannot be overridden. Something like this:

public var defaultProperty:Int { final get; final set; } // This property cannot be overridden

// Translates to:

public var defaultProperty(default, default):Int;

So, for people that don't want a function call, and won't override the getter/setter, they should put the final modifier.

What do you think?

marcelEuchnerMartinez commented 2 years ago

message deleted

ShaharMS commented 1 year ago

Maybe for the default case, we could check for the final modifier in the getters and setters. With this, if the user doesn't provide a getter or a setter function (an Autoproperty), it should behave like a field access. Also, that getter or setter cannot be overridden. Something like this:

public var defaultProperty:Int { final get; final set; } // This property cannot be overridden

// Translates to:

public var defaultProperty(default, default):Int;

So, for people that don't want a function call, and won't override the getter/setter, they should put the final modifier.

What do you think?

Maybe its just me, but i think this might be more clear than the usage of final here:

public var defaultProperty:Int { get -> default; set -> default; } // This property cannot be overridden

// Translates to:

public var defaultProperty(default, default):Int;

Then, you could also do:

public var unsettable:Int { get -> default; set -> never; }
GasInfinity commented 1 year ago

Maybe its just me, but i think this might be more clear than the usage of final here:

public var defaultProperty:Int { get -> default; set -> default; } // This property cannot be overridden

// Translates to:

public var defaultProperty(default, default):Int;

Then, you could also do:

public var unsettable:Int { get -> default; set -> never; }

Hi, I understand your point, but, what I am trying to do is not to have a 1 to 1 translation to the current property syntax. I am proposing a totally new syntax with some new behaviour, maybe the other syntax could be deprecated and removed as a breaking change in a major release. But that is out of scope of this proposal.

I think that the arrow syntax should only be used for implementations of the getter or setter, it is not only because it may be easier to parse (You just need to expect a semicolon or an arrow with expression. Semicolon = Autoproperty, Expression = Implementation/Function), it's also because I think it is cleaner and easier.

Also, I still think that the behaviour of default or never should be achieved with modifiers to an Autoproperty. Knowing this, I think that the default behaviour should be achieved by making/overriding a property as an Autoproperty with the final modifier, so we don't have to include more modifiers.

With this proposal as it is, you could achieve the behaviour of never by not having a setter and never override the property to have a setter. If you would like to control that behaviour, so it also cannot be overriden, I could propose a new, never modifier for the getter/setter that achieves that.

// This
public var unsettable:Int { final get; never set; }

// Would be the same as this
public var unsettable:Int { final get; }

// But If someone tries to implement the setter in a derived class, it will error with something like "unsettable property cannot have a setter because X declares it as unsettable".

The only thing is that it would be pointless in a lot of situations, like in static properties, those cannot be overriden, so, that modifier would just work on normal instance properties. But I think if someone does not want to have a setter I think it would be best to not implement it directly (In any of the two forms) in any class.

There's other modifier that I haven't discussed, dynamic. My stay is that I think properties don't need dynamic functions.

As always, I am going to ask, What do you think?

ShaharMS commented 1 year ago

Thinking about it I think I like your implementation more :) One question - I find myself overriding getters & setters from time to time. How would that be possible under this implementation?

GasInfinity commented 1 year ago

You would override it like a normal function, with the override modifier.

class Car
{
  private var baseSpeed: Int;
  public var Speed: Int { get -> baseSpeed; }
}

class FasterCar extends Car
{
  public var Speed: Int { override get -> baseSpeed * 2; }
}

The only edge case that comes to my mind is if we should allow this:

class AdjustableCar extends Car
{
  public var Speed: Int { override get; } // Do we create a backing field? Or should we error because we need to provide an implementation.
}

I'll update the proposal to cover this edge case, I think properties that already have an implementation shouldn't be overriden as an autoproperty.

Aidan63 commented 1 year ago

I like this as well, I usually ignore properties as their syntax is very odd and requiring get_ or set_ leads to inconsistent styling if your codebase isn't using snake case.

I wonder if there would be any pushback over adding a new property keyword, might avoid overloading var and make it easier to use some different syntax. A very quick idea using anonymous structures and a property keyword.

class MyClass {
    var myVar : Int;

    public property myProp1 = {
        get : () -> myVar,
        set : v -> myVar = v
    }

    public property myProp2 = {
        get : () -> myVar * 2
    }
}

class MySubClass extends MyClass {
    public override property myProp2 = {
        get : () -> myVar * 3
    }
}

There is already the iterator and iterable structure which classes need to unify with to support, so maybe the anonymous structures would need to unify against one of few types to be a valid property.

typedef Getter<T> = {
    final get : Void->T;
}
typedef Setter<T> = {
    final set : T->T;
}
typedef FullProperty<T> = {
    > Getter<T>,
    > Setter<T>
}

I have not thought about access modifiers, inline, final, etc, etc, or what should happen if the compiler isn't able to infer the type based on the getters and setters, so not not very well thought out! But another syntax idea in the ring based on having a property keyword.

AndrewDRX commented 4 months ago

How about having the override on the property as a whole:

class AdjustableCar extends Car
{
  public override var Speed: Int { get -> ...; set -> ...; }
}

And it would require that all property accessors that were specified on the base class must also be specified on the derived class. But have the capability to call super to fall back to the base logic on accessors that don't need new logic in the override.

GasInfinity commented 4 months ago

How about having the override on the property as a whole:

class AdjustableCar extends Car
{
  public override var Speed: Int { get -> ...; set -> ...; }
}

And it would require that all property accessors that were specified on the base class must also be specified on the derived class. But have the capability to call super to fall back to the base logic on accessors that don't need new logic in the override.

That's another option too, it communicates that the property has a setter if you're reading the code. It's basically preference on this one, the override could be outside or inside. When you put it inside, you're communicating that you're only overriding a getter, omitting if it had a setter or not, whilst if you put it outside, you're communicating that you're overriding the entire property. I don't mind, it depends on what people do want.

hughsando commented 4 months ago

You could use 'default' to only override a single part, public override var Speed: Int { default; set -> ...; } which fits with existing usage.

ShaharMS commented 3 months ago

Another thing to consider (I don't remember seeing anyone talking about this): in the previous syntax, a value parameter for the setter was explicitly provided:

function set_property(newValue:String) ...

If the proposed syntax is accepted, we need to either:

GasInfinity commented 3 months ago

@ShaharMS In the proposal I already say that the argument passed to the setter has the implicit name of 'value'. It doesn't have to be a keyword, it would be just a normal identifier. If people really want to change the name of the parameter maybe parens could be added to the setter( set(other:T) {}) to set the argument name but it may be a little clunky.