haxe-evolution icon indicating copy to clipboard operation
haxe-evolution copied to clipboard

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

Open GasInfinity opened this issue 2 years ago • 13 comments

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

GasInfinity avatar Mar 13 '22 14:03 GasInfinity

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).

back2dos avatar Mar 14 '22 12:03 back2dos

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?

GasInfinity avatar Mar 14 '22 15:03 GasInfinity

message deleted

marcelEuchnerMartinez avatar May 30 '22 05:05 marcelEuchnerMartinez

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; }

ShaharMS avatar Aug 15 '22 21:08 ShaharMS

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?

GasInfinity avatar Aug 16 '22 09:08 GasInfinity

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?

ShaharMS avatar Aug 17 '22 10:08 ShaharMS

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.

GasInfinity avatar Aug 17 '22 11:08 GasInfinity

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.

Aidan63 avatar Dec 19 '22 12:12 Aidan63

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.

AndrewDRX avatar Mar 04 '24 04:03 AndrewDRX

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.

GasInfinity avatar Mar 05 '24 15:03 GasInfinity

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

hughsando avatar Mar 06 '24 01:03 hughsando

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:

  • come up with an exclusive keyword/parameter for retrieving the new value, which may be a little confusing, or restrict is as a field name
  • provide it explicitly in the setter in some way, which might make properties a little more bloated.

ShaharMS avatar Mar 23 '24 22:03 ShaharMS

@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.

GasInfinity avatar Mar 23 '24 22:03 GasInfinity