Parameterized Properties or Named Subscripts

Hi swift-evolution,

I have an idea for (what I think is) a minor addition to the language. Please forgive me if this has already been discussed at length, all I could find were a few messages from a year and a half ago. :)

One of the things I really like about Swift is that it provides native support for properties. This means that in addition to not having to write getters or setters for every public property on every one of your types when no custom behaviour is needed, it creates a syntactic distinction between methods and properties at the point of use. It makes sense that you would be able to set the value of someProperty on someInstance using the same syntax as you would any local variable rather than using a method called setSomeProperty, for instance.

However, one minor shortcoming of the Swift property system in my opinion is that in certain cases, things that really feel like properties cannot be expressed as such in the language, and we have to fall back on the “old” method of getter and setter methods. Take UIButton, for example. UIButton handles a lot of different cases. It can contain an image, or a label, and then on top of that its appearance can be different depending on which control state it is in. In order to deal with this, it provides setter and getter methods to configure all of this state including the title, font, image, background image and more, all of which take a UIControlState. Now, there’s nothing wrong with this, but it seems to me to be inconsistent with the design of properties in Swift.

So, I propose adding the ability to declare “parameterized properties”, which are exactly as they sound. Instead of writing something like this:

extension MyClass {
    func myProperty(for configuration: ConfigurationData) -> Type {
        ...
    }

    func setMyProperty(_ value: Type, for configuration: ConfigurationData) {
        ...
    }
}
let instance: MyClass = ...
instance.setMyProperty(someValue, for: .someConfiguration)

One could write something like this (strawman syntax):

extension MyClass {
    var myProperty(for configuration: ConfigurationData): Type {
        get {
            ...
        }
        set {
            ...
        }
    }
}
let instance: MyClass = ...
instance.myProperty[for: .someConfiguration] = someValue

Ignoring syntax for a moment, there are a few specifics about this idea I’d be interested in feedback on.

First of all, should these be allowed on stored properties, for use with did/willSet blocks? I can’t think of an instance where this would be useful but I also don’t see why we shouldn’t allow it.

Second of all, should default argument values be allowed? Similarly, I don’t see why not. Furthermore, if all its arguments had default values, it would make sense that you would access a parameterized property in the same way you would a normal property, with no square brackets.

And finally, would it be better to express this idea as named subscripts instead of parameterized properties? Personally I don’t think so because I think the kinds of things this feature would be most useful for share more similarities with properties than with subscripts. But I’m open to alternative views on this.

Let me know what you think!

2 Likes

I've run into this problem recently, but was quite happy to fix it using simple subscripts:

class Context<T: class, U> {
    private let object: T
    private let context: U
    
    init(object: T, context: U) {
        self.object = object
        self.context = context
    }
}

extension MyClass {
    subscript(configuration data: ConfigurationData) {
        // These could reasonably be cached
        get { return Context(object: self, context: data) }
    }
}

extension Context where T: MyClass, U: ConfigurationData {
    var myProperty: Type {
        get {
            // ...
        }
        set {
            // ...
        }
    }
}

let object = MyClass()
object[configuration: data].myProperty = info

I don't see there being enough value in introducing this as a language feature when it can be reasonably expressed now using subscripts. I think using subscripts for this is an acceptable use of them.

1 Like

Thanks David, that’s an interesting idea and I think it definitely seems Swiftier than getter and setter methods! After considering it briefly, my main issue with this solution shows up in the last line of your sample. Subtle as it may be, I think there’s an important distinction to be made between setting myProperty on an instance of MyClass using configuration x, and setting myProperty on a Context value that just so happens to have been retrieved from an instance of MyClass using configuration x. In other words, I consider the target use case of a parameterized property to be where the property is an inherent part of its parent type, but can vary depending on some amount of state, and so must be set in anticipation of future state. This solution seems to obfuscate that. Additionally (and importantly), it won’t work with code completion because in order to set these context properties, one needs to be familiar with the fact that the type has this configuration subscript.

Thanks for your input, Erik. To me the value is in providing a consistent and idiomatic way of expressing something library designers (including those at Apple) are already doing through slightly unideal workarounds. I consider current subscripts to be an example of one such unideal workaround for this problem, because they force you to use the name of the “property” in the argument label, rather than as a property of the type.

I do agree that this is not a huge priority, as has been borne out by the fact that I’m the first to mention it in 1.5 years, haha. It’s a nitpick, not a lifesaver.

1 Like

Someone (basically :wink:) asked about this on Stack Overflow today.

It'd be really nice if value type instances "supported this" the way that everything else "does" now that we have static subscripts.