[swift-evolution] [Proposal Draft] property lists
Matthew Johnson
matthew at anandabits.com
Mon Jan 11 11:17:58 CST 2016
> On Jan 11, 2016, at 10:21 AM, Janosch Hildebrand <jnosh at jnosh.com> wrote:
>
> Hi Matthew,
>
> Just a few quick comments:
>
> propertylist:
> Not a big fan of the name either, `propertyalias` came to mind but that sounds like it's only for a single property.
If you think of something better please let me know!
>
> @propertylist:
> I think this is mostly redundant and the proposal would be improved by its removal.
Removing it would require a redundant list of property names in some cases. Why do you think that is better?
>
> propertylist syntax:
> I don't think the current syntax. Using `:` feels wrong as I instinctively expect a type to it's right.
>
> How about: `propertylist fooProps = (aProp, bProp)`
That syntax could work.
>
> Default values in propertylist:
> Strongly against this. Only applicable to initializer use case and feels generally out of place.
It is extremely important to the initializer use case. There is no way to provide a default for `let` properties in the initializer without allowing this. I would not have written this proposal at all if it weren’t for a desire to improvise the initializer use case.
>
> I have also looked at the other two related proposals but I'm far from having a final opinion on any of the three.
> I'm also posting my current very short and very rough impressions of the three related proposals here as I feel they are strongly related and I don't have enough to say about the other two to really justify separate posts:
Thanks. I appreciate your feedback!
>
> Parameter forwarding:
> This is the proposal I like the least. I'm not a fan of the syntax and it mostly saves some minor typing at the cost immediately knowing what the actual arguments are.
Have you used a language with a similar feature (such as tuple packing and unpacking in a dynamic language)? It is quite useful. IMO, removing the clutter of each argument, type, and default value makes it much more clear that simple forwarding is happening.
The complete parameter list would still appear in generated documentation, autocomplete, etc. An IDE could also provide an option to view the full parameter list in the function declaration itself.
>
> Partial initializers:
> My dislike of parameter forwarding carries over to this but otherwise I find the basic idea quite nice.
> My bigger issue here is that I currently don't see a huge use case for this that isn't already served by initializer delegation. Yes this seems to cover additional cases but I'm not sure it justifies it's existence. It makes a lot more sense when considered as part of the memberwise initialization trifecta with parameter forwarding but like I said that's the part I don't really like.
Initializer delegation requires the initializer you call to be a complete initializer. It is not uncommon to have more than one designated initializer that need to share initialization logic. There are techniques to factor out some of this logic today, but it would be much easier with partial initializers.
>
> Property Lists:
> Like with partial initializers I find this to be an interesting idea and this seems to have other potential use cases apart from memberwise initialization. Then again I'm unsure if this exact proposal is the way to go here. I'd really prefer to see more abstract discussion of this idea before committing to any specifics.
>
>
> Particularly here but also in general for all three proposals I feel that we might be better served by letting the language form further and waiting for more general pieces to fall into place before charging ahead in these areas. None of these require stopgap solutions from my point of view and I at least feel like I could evaluate this much better once I have an idea of what Swift is going to look like at version 4, 5, ...
> From reading the Swift team's comments it seems like future improvements like variadic generics might make a potential difference here and I imagine the macro system might as well.
These proposals are partly a response to the comments Dave and Joe made about building on more general underlying features.
Partial initializers are a feature that would need to stand on its own, it would not be enabled by any other features. As noted in the proposal, it might actually help to prepare the initialization model to support initialization of extensions and protocols with stored properties.
I don’t believe variadic generics would cover the full set of forwarding capabilities I would like to see. Particularly forwarding of default values, but also forwarding a subset of parameters. I know you are not a fan of the forwarding idea so that may not matter much to you.
The property list idea could probably be implemented with a powerful-enough macro system. My opinion is that it would be nice to solve this problem in Swift 3 if possible. The feature could be replaced by a macro down the road if it becomes possible to write it that way.
>
>
> I hope this doesn't sound too negative, you clearly invested a significant amount of work into these proposals and I value the effort. It helps me better understand the problems people see and what potential solutions might look like and stirs a lot of healthy discussion.
> So don't let yourself be discouraged if I don't feel that changes in this area are required just yet :-)
That’s a fair opinion. The discussion has been healthy indeed! IMO it has at least validated that there is broad support for doing something to address these problems.
One of the big critiques of the Flexible Memberwise Initialization proposal was that it wasn’t powerful enough to support all the use cases people want to solve. I think there is a lot of demand to solve these problems and many people would like to see them solved in Swift 3.
Matthew
>
> - Janosch
>
>> On 11 Jan 2016, at 04:44, Matthew Johnson via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>
>> I have always considered the Flexible Memberwise Initialization proposal to be just a first step (as evidenced by the many future enhancements it discussed). Its review has inspired new ideas and helped to shape my vision of the best long-term solution. My final thoughts about the review can be found here: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html>
>>
>> Property lists is the third in a series of three proposals describing general features that can work together to form a complete solution.
>>
>> The proposal drafts can be found at the following links:
>>
>> * Parameter forwarding: https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md <https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md>
>> * Partial initializers: https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md <https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md>
>> * Property lists: https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md <https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md>
>>
>> Matthew
>> Property Lists
>>
>> Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-property-lists.md>
>> Author(s): Matthew Johnson <https://github.com/anandabits>
>> Status: Awaiting review
>> Review manager: TBD
>> Introduction
>>
>> This proposal introduces the propertylist declaration. Property lists provide concise syntactic sugar for declaring memberwise partial initializers and memberwise computed tuple properties.
>>
>> NOTE: I do not love the name “property lists” for the feature or the keyword but haven’t thought of anything better. Suggestions are welcome.
>>
>> Swift-evolution thread: Proposal Draft: Property Lists <https://lists.swift.org/pipermail/swift-evolution>
>> Motivation
>>
>> I believe the review of the Flexible Memberwise Initialization <https://github.com/apple/swift-evolution/blob/master/proposals/0018-flexible-memberwise-initialization.md> proposal demonstrated a strong demand for concise yet flexible memberwise initialization. The discussion highlighted several areas where that proposal fell short:
>>
>> Clarity regarding which parameters receive memberwise initialization.
>> Control over which parameters receive memberwise initialization.
>> Control over specific memberwise parameter ordering.
>> Control over parameter labels.
>> Control over default parameter values, especially for let properties.
>> It is a very narrow, special case feature.
>> This proposal builds on the Partial Initializer <https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md> proposal to solve these problems using a more general underlying mechanism. It enables truly flexible memberwise initialization.
>>
>> Property lists also support other memberwise features, beginning with memberwise computed tuple properties.
>>
>> Proposed solution
>>
>> There are two ways to specify a property list. The basic mechanism is a property list declaration that looks as follows:
>>
>> propertylist configProperties: aPropName, customLabel anotherPropName = 42
>> All properties mentioned in the property list declaration must be visible at the site of the declaration.
>>
>> A propertylist attribute also exists for property declarations to support cases where a list of property declarations should also be treated as a property list:
>>
>> @propertylist(aPropertyListIdentifier) let prop1, prop2: Int
>> A property list can contain any kind of property, including those with behaviors. However, when certain kinds of properties are included it will not be possible to synthesize a partial memberwise initializer and / or a setter for the computed tuple property.
>>
>> Synthesized elements
>>
>> A typealias will always be synthesized:
>>
>> It will have a name matching the identifier of the property list, with the first character transformed to upper case.
>> It will be a tuple type containing labels and types matching those specified in the property list.
>> A computed tuple property will always be synthesized:
>>
>> It will always include a getter.
>> Visibility of the getter will match the visibility of the least visible getter.
>> It will contain a setter as long as all of the properties are settable.
>> Visibility of the setter will match the visibility of the least visible setter.
>> A paritial initializer will only be generated if:
>>
>> All properties are stored properties.
>> None are let properties with an initial value.
>> None have a behavior which is incompatible with phase 1 initialization.
>> Visibility of the partial initializer will match the least visible setter for structs. Partial initializers for classes are always private (as specified by the partial initializer proposal).
>> The property list is declared in the main body of the type, not an extension, unless the type is a struct.
>> If stored properties are allowed in extensions and / or protocols in the future, all properties included in the list must be declared within the same body as the property list for a partial initializer to be synthesized (either the main body of the type or the body of the same extension or same protocol).
>> The structure of the synthesized elements is as follows:
>>
>> Ordering of partial initializer parameters and tuple members will match the order of the property list declaration.
>> The external parameter labels and tuple labels will match the label specified in the property list if it exists and the property name otherwise.
>> The default value for partial initializer parameters will be the default value specified in the property list if it exists and the initial value of the property otherwise (if that exists). If neither exist the parameter will not have a default value.
>> Visibility of a synthesized members is capped at internal unless public is explicitly specified. If public (or internal) is explicitly specified, all properties referenced must have getter and setter visibility of at least the specified access level or a compiler error will result.
>>
>> Examples
>>
>> Basic example
>>
>> public struct S {
>> let i: Int = 42
>> public var s: String = "hello"
>> public var d: Double
>>
>> // user declares:
>> public propertylist custom: dLabel d = 42, s
>>
>> // compiler synthesizes:
>> public typealias Custom = (dLabel: Double, s: String)
>> public var custom: Custom {
>> get { return (dLabel: d, s: s) }
>> set { (d, s) = newValue }
>> }
>> public partial init custom(dlabel d: Double = 42, s: String = "hello") {
>> self.d = d
>> self.s = s
>> }
>> }
>> Including a let with a initial value
>>
>> struct S {
>> let i: Int = 42
>> let s: String = "hello"
>> var d: Double
>>
>> // user declares:
>> propertylist custom: dLabel d, s
>>
>> // compiler synthesizes:
>> typealias Custom = (dLabel: Double, s: String)
>> var custom: Custom {
>> get { return (dLabel: d, s: s) }
>> }
>> // NOTE: no setter because a `let` was included
>> // and no partial initializer because the `let`
>> // has an initial value.
>> }
>> Including a lazy property
>>
>> struct S {
>> let i: Int
>> var s: String
>> lazy var d: Double
>>
>> // user declares:
>> propertylist custom: dLabel d, s
>>
>> // compiler synthesizes:
>> typealias Custom = (dLabel: Double, s: String)
>> var custom: Custom {
>> get { return (dLabel: d, s: s) }
>> set { (d, s) = newValue }
>> }
>> // NOTE: no partial initializer because a `lazy var` was included.
>> }
>> Including a var with a private setter
>>
>> struct S {
>> let i: Int
>> var s: String
>> private(set) var d: Double = 42
>>
>> // user declares:
>> propertylist custom: dLabel d, s = "hello"
>>
>> // compiler synthesizes:
>> typealias Custom = (dLabel: Double, s: String)
>> private(set) var custom: Custom {
>> get { return (dLabel: d, s: s) }
>> set { (d, s) = newValue }
>> }
>> // NOTE: The initial value for `d` is used as a default
>> // parameter value because a different default parameter value
>> // was not specified.
>> private partial init custom(dlabel d: Double = 42, s: String = "hello") {
>> self.d = d
>> self.s = s
>> }
>> }
>> Including a computed property
>>
>> struct S {
>> let i: Int
>> var s: String
>> var d: Double {
>> get { return getValueFromSomewhere() }
>> set { storeValueSomewhere(newValue) }
>> }
>>
>> // user declares:
>> propertylist custom: dLabel d, s
>>
>> // compiler synthesizes:
>> typealias Custom = (dLabel: Double, s: String)
>> var custom: Custom {
>> get { return (dLabel: d, s: s) }
>> set { (d, s) = newValue }
>> }
>> // NOTE: no partial initializer because a computed property was included.
>> }
>> Using the @propertylist attribute
>>
>> struct S {
>> @propertylist(custom) var s: String, d: Double = 42
>> private let i: Int
>>
>> // compiler synthesizes:
>> typealias Custom = (s: String, d: Double)
>> var custom: Custom {
>> get { return (s: s, d: d) }
>> set { (s, d) = newValue }
>> }
>> partial init custom(s: String, d: Double = 42) {
>> self.s = s
>> self.d = d
>> }
>> }
>> Using a property list declaration for memberwise intialization
>>
>> struct S {
>> var x, y, z: Int
>> let s: String
>> let d: Double
>>
>> propertylist randomGroup aString s = "hello", anInt x = 42
>>
>> // user declares
>> init(...randomGroup) {
>> y = 42
>> z = 42
>> d = 42
>> }
>>
>> // compiler synthesizes
>> partial init randomGroup(aString s: String = "hello", anInt x: Int = 42) {
>> self.s = s
>> self.x = x
>> }
>> init(aString s: String = "hello", anInt x: Int = 42) {
>> randomGroup.init(aString: s, anInt: x)
>> y = 42
>> z = 42
>> d = 42
>> }
>>
>> }
>> Using the property list attribute for memberwise intialization
>>
>> ```swift
>> struct S {
>> @propertylist(declaredTogether) var x, y, z: Int
>> let s: String
>> let d: Double
>>
>> // user declares:
>> init(…declaredTogether) {
>> s = “hello”
>> d = 42
>> }
>>
>> // compiler synthesizes:
>> partial init declaredTogether(x: Int, y: Int, z: Int) {
>> self.x = x
>> self.y = y
>> self.z = z
>> } init(x: Int, y: Int, z: Int) {
>> declaredTogether.init(x: Int, y: Int, z: Int)
>> s = “hello”
>> d = 42
>> }
>>
>> }
>>
>> Implicit property lists
>>
>> It may be desirable to have several implicit property lists available, such as one that includes all properties of the type. Properties would appear in any implicit property lists in declaration order.
>>
>> The specific details of what implicit property lists should be available and what they should be called is a good topic for bikeshedding.
>>
>> Detailed design
>>
>> TODO but should fall out pretty clearly from the proposed solution
>>
>> Impact on existing code
>>
>> This is a strictly additive change. It has no impact on existing code.
>>
>> Alternatives considered
>>
>> We could live without this syntactic sugar. There are several reasons the language is better with it:
>>
>> It is much more convenient than manually writing memberwise partial intializers. It does not include any unnecessary information, thus making the details more clear.
>> It gives us the memberwise computed tuple properties for free. This would would not be possible when writing memberwise partial initializers manually.
>> It scales to any new memberwise features that might make sense for a type.
>> As always, the propertylist keyword is game for bikeshedding. The use of : to separate the identifier from the list of properties could also be game for bikeshedding. Also, as mentioned previously, the implicit property lists are game for bikeshedding.
>>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160111/c04a9d62/attachment.html>
More information about the swift-evolution
mailing list