Pitch: Property Delegates

I thought this was already included in the pitch. If it wasn't it definitely should be. It would be troubling to me to have to expose the backing store at the same level of visibility and not be able to hide it. That isn't necessary when writing this pattern manually.

1 Like

After a bit more thinking in the shower, here are some additional alternatives to by. They all basically attempt to describe the relationship between the property and the delegate:

uses
using
utilizes
utilizing
adopts
adopting
2 Likes

"Searchability" is a concern that comes up often, but when I've investigated the real effectiveness of Google on searching for common short words as programming terms, it does a good job in practice:

  • The first hits for kotlin by bring up Kotlin's property delegates feature, analogous to one being proposed here
  • The first hits for swift some bring up discussion of Swift optionals
  • Even common lisp the manages to find Common Lisp's THE special form as the second hit.

Searchability and discoverability is a valid concern, but it looks like in practice, preemptively picking a long or unusual keyword solely for the sake of searchability is not really necessary.

16 Likes

Even without a specific use case, I don't think we should be seeking to limit the applicability here: there's nothing about reference types that conflict with property delegates, and there's no reason to expect that @propertyDelegate wouldn't do what the user expects with a class.

Doug

2 Likes

I agree that searchability shouldn't be a primary factor. My objection to by is more that I haven't yet put together a plausible story for what it means. It feels arbitrary, perhaps as if we're choosing brevity at the expense of clarity. I think it would be better to pick a somewhat longer keyword that reads better at the use site and better suggests a meaning. Given the use, it doesn't seem like the alternative keywords in the list above (even the longest, utilizing) would be found to be too long.

5 Likes

I'm not sure if this is already meant to be implied by the terminology in the proposal, but is this feature restricted to type members, or does it also apply to any VarDecl, including function-local and global variables?

I don't think I'm suggesting that you go as far as call it @varDelegate since properties are probably the 99.99% case here (just as local/global variables very rarely have accessors), but for the sake of completeness, it might be good to have the proposal say whether or not that's possible or intentionally forbidden.

The reason I suggested this is because the backing storage can change the semantics of the property in a way that isn’t represented by its type. It seems like this could be very suprising. However as @beccadax pointed out, it would still be possible to implement property delegates that have reference semantics so I guess agree. The extra complexity of banning them isn’t worth it. Reference-semantic property delegates still seem like they could easily be a footgun, but hopefully people will use good judgment and not run into the potential trouble.

"Properties" is a bit of a catch-all term in Swift, so "yes": you'll be able to use property delegates for global properties, member properties, and local properties within a function/closure/etc. I will clarify this in the proposal, thank you!

They're not angry ;) LLDB's use of $ will take precedence, and with the exception of local variables that have property delegates, Swift gives you another way to name the backing storage property by qualifying it (e.g., ModuleName.$foo or self.$foo).

lazy is hard to fully supersede, but I'd love to follow up with a library solution for @NSCopying along with deprecation.

(I'll reply to the syntactic concerns separately)

Doug

Are the delegate types still usable as regular types, in addition to being property delegates?

// implicitly creates `foo`
var $foo = Lazy(initialValue: 17)
// just a regular var, no `$bar` created?
var bar = Lazy(initialValue: 17)

If I create a property delegate using the $name spelling, does the property get the same access level?

// is `baz` public, too?
public var $baz = Lazy(initialValue: 17)

Right, you wouldn't be able to initialize the property with self because self isn't available until the property has already been initialized. I've had a few ideas of how to grow in this direction... the one that fits most closely with this design is to pass self to a special subscript of the delegate type instead of going through the value property. For example,

@propertyDelegate
struct Synchronized<T> {
  var stored: T

  subscript<InSelf: AnyObject>(instanceMemberOf inSelf: SelfType) -> T {
    get {
      return synchronized(inSelf) { return stored }
    }

    set {
      synchronized(inSelf) { stored = newValue }
    }
  }
}

The thing that annoys me about this formulation is that it only really works for class types, because you can't pass the "self" of a value type to a subscript as &inout. I suspect there's an answer in here somewhere.

Right now, the type named after by must be generic with a single type parameter (which will always be the type of the original property).

I don't see a great way to do this based on the restriction I mentioned above.

Doug

The proposal limits the use of behaviors to properties declared with var. My thinking here is that let is fairly restrictive already: let conveys a strong guarantee that the value won't change on you. This is why you cannot have a computed let (which is what let x by Y would end up being), and I think that logic extends to properties with delegates.

Doug

5 Likes

It is backwards-deployable; added at https://github.com/DougGregor/swift-evolution/blob/property-delegates/proposals/NNNN-property-delegates.md#backward-compatibility.

Doug

2 Likes

I like this pitch. It's straightforward and simple.

In response to syntax concerns, by makes sense to me. It's also an established convention thanks to Kotlin. However, since an alternative did come to mind, I may as well toss it into the wind:

var x: Int via Lazy = 49
5 Likes

Yes, they are "just" normal types that can also be used as property delegates.

$baz is internal. You can make it public with by public; see https://github.com/DougGregor/swift-evolution/blob/property-delegates/proposals/NNNN-property-delegates.md#proposed-solution for an example.

Doug

I like this direction, but I'll challenge you to see what it would involve to take it one step further to:

var label: UILabel by IBOutlet()

In other words, bring all of the logic into an IBOutlet property delegate type.

Doug

5 Likes

If Swift is eventually able to recognize pure functions perhaps we could have computed let properties and property delegates could have a let value property instead, allowing these delegates to be used in let declarations. I'm not sure what the use cases for that would be though.

1 Like

It's already in the pitch (e.g., by private to lower the access, by public to raise it above internal), but at least 3 people missed this, so I'll fix the proposal!

Doug

I prefer via

5 Likes

Awesome, thanks for clarifying! The pitch only mentioned the public use case. I assumed private would also work but that part wasn't 100% clear to me.

To be clear, the proposal has specific examples of the by(...) syntax to invoke other initializers, both in the early examples (with Lazy(closure:...)) and in the section on https://github.com/DougGregor/swift-evolution/blob/property-delegates/proposals/NNNN-property-delegates.md#initialization-of-synthesized-storage-properties .

However, we didn't really have a compelling example in the proposal, so people missed it. I've pulled in Harlan's UserDefaults example as https://github.com/DougGregor/swift-evolution/blob/property-delegates/proposals/NNNN-property-delegates.md#proposed-solution

Doug

2 Likes