[swift-evolution] [Proposal] Property behaviors

John McCall rjmccall at apple.com
Tue Jan 19 18:11:58 CST 2016

> On Jan 19, 2016, at 2:46 PM, John McCall via swift-evolution <swift-evolution at swift.org> 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].
> "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()
> The behavior list has to match exactly (or maybe as sets?).
> 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.
> The initializer name, if bound, expands to the original expression within the behavior.  Maybe it should be coerced to type T first?  Not sure.

I feel like there are two possible implementation models here: type instantiation and member instantiation.  The type instantiation model has the advantage of allowing generic programming over the complete lazy type, but probably means we end up creating a lot of redundant stuff, some of which we’d have trouble eliminating.  The member instantiation model is tricky and more limiting in terms of what you can do polymorphically directly with behavior types, but (1) I don’t think that’s crucial and (2) it does seem implementable.

The member-instantiation model looks like this:

Conceptually, a behavior declaration creates a generic behavior value type:
  struct [weak,lazy]<T> {

That behavior declaration always contains an implicit “value” member:
  var value : T {

Any accessors in the behavior body are considered to be accessors of the value member.  Everything else is just a member of the behavior type.

An expression within the main behavior declaration is “instantiated” if it refers to any of the bound macro parameters (other than the type T).  A function body within the main behavior declaration is instantiated if:
  - it contains an instantiated expression,
  - it is an initializer, and the initializer expression of a stored property contains an instantiated expression,
  - it uses a function within the main behavior declaration that is instantiated, or
  - it uses an accessor within the main behavior declaration that is instantiated.

An instantiated member or accessor of the behavior type may not be used outside of the behavior body except as directly applied to a particular behavior object.

We can still separately type-check and even generate SIL for instantiated members.  Type-checking works by saying that the property name is an opaque string literal and the initializer is an opaque r-value of type T; the type-checker would have to do the latter coercion for each use, but that seems better semantically and for type inference anyway.  When an instantiated function was used from an uninstantiated context, SILGen would just create SIL builtins that fetch the name / initializer; instantiating would clone the body and replace those operations.  The type checker would require that instantiated functions can only be used from appropriate contexts, and the SIL verifier and IRGen would do the same.

The behavior type must be nullary-initializable (possibly with an instantiated initializer).  At the use site, the behavior is always expanded as:
  var _foo_behavior = [weak,lazy]<T>()

I’m not quite sure how extensions should work with initializers, because what I described above naturally allows behaviors to be overloaded based on the presence/absence of the initializer.  Either that’s disallowed, or you have to write:
  extension var [weak,lazy] _ : T = _ { … }
to clarify that (1) you mean the behavior that allows an initializer but (2) you can’t actually use the initializer expression in your implementation, because the member-instantiation model definitely doesn’t support doing that.

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

More information about the swift-evolution mailing list