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

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

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

GasInfinity
Copy link

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
Copy link
Member

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
Copy link
Author

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?

Also added some more notes about moving to this new property syntax if it gets accepted.
@skial skial mentioned this pull request Mar 16, 2022
1 task
GasInfinity added 3 commits March 17, 2022 15:28
Should we allow only setters without getters like now?
(Things that already exist with the current syntax, but with new syntax)
@marcelEuchnerMartinez
Copy link

marcelEuchnerMartinez commented May 30, 2022

message deleted

@ShaharMS
Copy link

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
Copy link
Author

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
Copy link

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
Copy link
Author

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
Copy link
Contributor

Aidan63 commented Dec 19, 2022

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
Copy link

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
Copy link
Author

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
Copy link
Member

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

@ShaharMS
Copy link

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.

@GasInfinity
Copy link
Author

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

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.

7 participants