[swift-evolution] [Proposal] Property behaviors

Joe Groff jgroff at apple.com
Tue Jan 19 19:47:35 CST 2016


> On Jan 19, 2016, at 4:28 PM, John McCall <rjmccall at apple.com> wrote:
> 
>> On Jan 19, 2016, at 3:10 PM, Joe Groff <jgroff at apple.com <mailto:jgroff at apple.com>> wrote:
>>> On Jan 19, 2016, at 2:46 PM, John McCall <rjmccall at apple.com <mailto:rjmccall at apple.com>> wrote:
>>> 
>>>> On Jan 13, 2016, at 2:07 PM, Joe Groff via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>> Thanks everyone for the first round of feedback on my behaviors proposal. I've revised it with the following changes:
>>>> 
>>>> - Instead of relying on mapping behaviors to function or type member lookup, I've introduced a new purpose-built 'var behavior' declaration, which declares the accessor and initializer requirements and provides the storage and behavior methods of the property. I think this gives a clearer design for authoring behaviors, and allows for a more efficient and flexible implementation model.
>>>> - I've backed off from trying to include 'let' behaviors. As many of you noted, it's better to tackle immutable computed properties more holistically than to try to backdoor them in.
>>>> - I suggest changing the declaration syntax to use a behavior to square brackets—'var [behavior] foo'—which avoids ambiguity with destructuring 'var' bindings, and also works with future candidates for behavior decoration, particularly `subscript`.
>>> 
>>> Syntax comments:
>>> 
>>> I still think these feel attribute-like to me, but if we’re not just going to use @lazy — and I agree that that does have some problems —I’m fine with [lazy].
>> 
>> I'm OK with using attribute syntax alongside the declaration approach.
>> 
>>> 
>>> "var behavior" is really weird to me, and the <T> doesn’t seem to fit and is pretty redundant in the common case.  How about this:
>>> 
>>>   "behavior" var-or-let "[" identifier-list "]" (identifier | "_") ":" identifier ("=" identifier)? ("where" generic-requirement-list)?
>>> 
>>> So, for example,
>>>   behavior var [lazy] _ : T where T : IntegerLiteralConvertible { … }
>>> 
>>> This is definitely taking the idea of “this is basically a macro” and running with it.  Think of the stuff between “behavior” and the optional “where” as being a pattern for the declaration.  So this pattern would match:
>>>   var [lazy] x: Int
>>> but not:
>>>   let [lazy] x: Int
>>> or:
>>>   var [lazy] x : Int = foo()
>> 
>> Good idea, I like this approach. However:
>> 
>>> The behavior list has to match exactly (or maybe as sets?).
>> 
>> Are you saying that there would be no ad-hoc composition of behaviors? This seems to imply that you'd need to implement every valid combination of behaviors by hand. That's a defensible position, given that it's easy to compose behaviors like "synchronized" in the wrong order, but significantly stifles behaviors like didSet/willSet that are more likely to be order-agnostic.
> 
> My first instinct is to say that ad-hoc composition is too treacherous to include in the first model, yeah.
> 
> I like the idea of having a model that works for literally everything that’s not pure-computed or pure-stored, but it seems tolerable to continue to build in things like willSet / didSet if it significantly simplifies the problem.  willSet / didSet have some pretty custom behavior and dependencies on the container.  OTOH, maybe that kind of thing is a core requirement for some of the stuff we’re thinking of doing.

I think you can define a reasonable facsimile of willSet/didSet under my proposal, but I'm willing to believe there are edge cases I'm missing. What sort of custom behavior do you have in mind?

-Joe

> 
>>> The property name, if bound, expands to a string literal within the behavior.
>>> 
>>> The type name is always a generic parameter.  This interferes with the ability to make a pattern that only matches a concrete type, but I think that’s okay.
>> 
>> Seems reasonable, since unconstrained behaviors are likely to be the 95% case. Being able to match concrete types is something we ought to be able solve uniformly with the same limitation on constrained extensions.
> 
> Yeah.
> 
>>> The initializer name, if bound, expands to the original expression within the behavior.  Maybe it should be coerced to type T first?  Not sure.
>> 
>> Yeah, JoeP brought up a good question about how 'var' type inference should work with initializer expressions. There are two possible models I can see:
>> 
>> - We infer the type of the initializer independent of any applied behaviors, and raise an error if the behavior can't be instantiated at the given type.
>> - We add generic constraints from the behavior declaration(s) to the contextual type of the initializer.
>> 
>> In support of the latter approach, 'weak' properties currently factor their Optional constraint into type inference ('weak var foo = Foo()' gives you a property of type Foo?), and 'weak' has been raised as a candidate for eventual behavior-ization. The downside, of course, is that with arbitrary user-defined behaviors with arbitrary generic constraints, there's yet another source of potential surprise if the type context of behaviors changes the type-checking of an expression.
> 
> Yeah, especially because the initializer could be used in multiple places in the behavior.  Coercing the initializer seems a lot less surprising.
> 
> John.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160119/9695e0fd/attachment.html>


More information about the swift-evolution mailing list