[swift-evolution] [Proposal] Allow scoped properties from within computed properties

Xiaodi Wu xiaodi.wu at gmail.com
Sat Jan 14 10:55:36 CST 2017


On Sat, Jan 14, 2017 at 9:36 AM, Joseph Newton <jnewto32 at gmail.com> wrote:

> Thank you for your responses Xiaodi and David,
>
> I believe you can do this using didSet without the `_bar`:
>> class Foo {
>>     public var bar: String? {
>
>         didSet {
>>             if (bar?.characters.count ?? 0) < 100 {
>>                 bar = nil
>>             }
>>         }
>>     }
>> }
>
>
> Yes, although this is a perfectly valid way of implementing this, I'm not
> a big fan of implementing this functionality in this way. Particularly, I
> don't like the concept of validating data after setting it, and then
> re-setting the property if the validation failed. I would much rather
> implement it in a manner where it's conditionally set depending on
> validation.
>
> It's an interesting idea, for sure. Because of the persistent storage
>> required for `_bar`, I *think* one way to look at what you're proposing
>> is essentially as a way to allow for a variable to be declared publicly
>> as one type, be implemented as another (anonymous, in this case) type under
>> the hood, and to specify a mechanism for automatically converting between
>> the two types.
>
>
> Essentially, yes. The programmer would have the option of using these
> nested properties to be able to add custom storage or functionality to any
> computed property that they please.
>
> (That said, there is a recurrent theme on this list where people ask for
>> new support for encapsulating members from visibility in ever smaller
>> scopes. I continue to be not in favor of the new `private`, but even
>> allowing its existence, providing all the permutations of
>> type/file/extension visibility is (afaict) and should continue to be (imo)
>> a non-goal. The distinction between public and internal not only avoids
>> pollution in your autocomplete list but also provides important safety
>> wins, and I think those gains are increasingly limited the finer we dice
>> these access levels.)
>
>
> I'm not sure that I quite understand what you mean by this. Would you mind
> elaborating?
>

See below.


>  I get that your proposal allows you to write one fewer declaration in
>> the case of an ad-hoc behavior than SE-0030 would. That is, in the case of
>> SE-0030, you'd have to declare both a behavior and a member that uses it;
>> in your case, you could declare just the member and implement the behavior
>> there. However, I think I'd want to see some concrete use cases that are
>> better served by this proposal than by SE-0030, which is the more general
>> solution as far as I can tell. In the two use cases you've mentioned, one
>> is served by `didSet`, and the other (`synchronized`) is a reusable
>> behavior for which SE-0030 offers the more elegant solution.
>
>
> Here are some additional uses cases for this proposal:
>
> *Implementing Objective-C's "__null_resettable"*
>
> class Foo {
>     var bar: UIColor! {
>         var _bar: UIColor = .clear
>
>         get { _return _bar }
>         set { _bar = newValue ?? .clear }
>     }
> }
>

In this use case, your proposal differs from what's currently possible only
in that `_bar` (presumably, after you write `private` in front of it, for
consistency with current rules about default access levels) would be
invisible even inside `Foo`. As I wrote to you above, avoiding "pollution"
within ever smaller scopes [in this case, the scope inside `Foo`] is
(AFAICT) and should be (IMO) a non-goal. To convince me that this use case
is worth the engineering effort and complexity of a new syntax, you'd also
have to convince me that having `_bar` visible within the entire scope of
`Foo` has practical and not merely theoretical footgun potential or some
other negative impact beyond the fact that autocomplete will show a member
you'd rather not see.

_Even if_ I were to buy that argument, I think there is a more general
solution that covers your use case. A few months earlier on this list,
several people discussed the possibility of same-module extensions allowing
stored properties, to very positive reception. There are numerous use cases
for that feature, but such a feature would also allow you to isolate `_bar`
and `bar` in its own extension, a common Swift idiom. It would also allow
you to group this implementation in the same extension with arbitrary
functions that need to see `_bar` (such as your `init(_:)` example below)
without exposing `_bar` to other initializer and methods that don't need to
manipulate it. This would be both more flexible and more fine-grained than
possible with your own proposal, and if I were to buy your argument that
`_bar` being visible where it's not needed is suboptimal, this would be the
optimal solution.

*Implementing a stack interface*
>
> class Foo<T> {
>     var currentItem: T? {
>         var storage = [T]()
>
>         get { return storage.last }
>         set {
>             if let newValue = newValue {
>                 storage.append(newValue)
>             }
>             else {
>                 storage.removeLast()
>             }
>         }
>     }
> }
>

I'm not sure I understand why this requires your proposed feature, other
than again that it hides `storage` from the rest of `Foo<T>`.


> *Validation before changing (for performance)*
>
> class SessionManager {
>     var session: Session {
>         var _session: Session
>
>         get { return _session }
>         set {
>             if newValue != _session {
>             // expensive teardown code for the old session
>
>             _session = newValue
>
>             // expensive setup code for the new session
>             }
>         }
>     }
> }
>

This can be implemented using `willSet` and `didSet` (taking your example
at face value, where it's not the actual swapping of newValue and _session
that is expensive but rather the teardown and setup, assuming `==` and `!=`
are implemented correctly, i.e. in such a way that `newValue == _session`
implies that the two fully instantiated values really are interchangeable
in every way that matters).

Lastly, I've been doing some additional thinking and I think that it would
> be beneficial to also setting values for these nested properties inside of
> *init* methods. For example, I work with C APIs a great deal and create
> Swift/ObjC wrappers for them a lot, I have a case where I provide an
> implementation as follows:
>
> class Wrapper {
>     private var _unmanaged: UnsafePointer<UInt8>
>     var text: String {
>         return String(cString: _unmanaged)
>     }
>
>     fileprivate init(_ unmanaged: UnsafePointer<UInt8>) {
>         _unamanged = unmanaged
>     }
> }
>
> It's implemented in this manner because the C APIs may change the
> characters pointed to by *_unmanaged* before the *text* property is
> accessed. With this proposal, this code could be implemented as follows:
>
> class Wrapper {
>     var text: String {
>         private var unmanaged: UnsafePointer<UInt8>
>
>         get {
>             return String(cString: unmanaged)
>         }
>     }
>
>     fileprivate init(_ unmanaged: UnsafePointer<UInt8>) {
>         self.text.unmanaged = unmanaged
>     }
> }
>

Unless I'm mistaken, this addendum to your idea can't be reconciled with
access control rules. A `private` var is hidden from _all_ methods not in
scope. One shouldn't (and probably can't, from an implementation
perspective) start making rules saying that certain `private` vars are
visible to initializers.

Using your proposed feature, you'd have to make `unmanaged` in this case
`fileprivate` and then implement the rest of `Wrapper` in an extension in a
different file if you want to hide `unmanaged` from those methods but leave
it visible to your initializer. By contrast, if you left `unmanaged` as a
private var directly inside `Wrapper`, you could hide `unmanaged` from any
methods implemented in an extension without having to move that extension
to a different file, probably a cleaner result if this kind of fine-grained
hiding from autocomplete is what you're after.


On Fri, Jan 13, 2017 at 4:01 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>
>> (Forward to the list if your early replied was intended to be sent there.)
>>
>>
>> On Fri, Jan 13, 2017 at 1:58 PM, Joseph Newton <jnewto32 at gmail.com>
>> wrote:
>>
>>> I believe that you're referring to SE-0030 Property Behaviors
>>> <https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md>,
>>> correct?
>>>
>>> I have indeed read over this proposal (I probably should have mentioned
>>> that in my first message) but I don't believe that it quite covers what I'm
>>> going for (or over-covers it).
>>>
>>
>> The reason I mentioned it was because the use case sounded vaguely
>> familiar. Reading over SE-0030, you'll see that one specific example is
>> synchronized property access. It demonstrates how SE-0030 would enable a
>> behavior called `synchronized` that would allow you to write, for any
>> arbitrary member foo, `public [synchronized] var foo: String`. It would
>> make it more than just easy to implement such a behavior for any member:
>> you would not have to write anything but `[synchronized]`.
>>
>> As to your other use case, the case of validating a set value, David
>> Sweeris has pointed out that `didSet` currently enables that functionality
>> pretty well.
>>
>>
>>> SE-0030 proposes making property behaviors requiring a specific
>>> declaration and implementation of a particular behavior just to be able to
>>> add stored properties to computed properties. Additionally, if you look at
>>> the "Properties and Methods in Behaviors" section of the proposal, you'll
>>> see that any stored property declared inside of a behavior is given the
>>> same access level as the property that is using the behavior. Although this
>>> could be convenient in some circumstances, my proposal calls for these
>>> stored properties to be private to the scope of the property declaration so
>>> as to avoid namespace pollution.
>>>
>>
>> I think you misunderstand that text, or maybe I do. It says that the
>> properties and methods are expanded into the containing scope, and that
>> they must be of _types_ that are visible at the call site. However, the
>> examples given for reimplementing lazy show very clearly that the
>> properties and methods can be declared private:
>>
>> ```
>> public var behavior lazy<Value>: Value {
>>   private var value: Value?
>>   // ...
>> }
>> ```
>>
>> SE-0030 was written before `fileprivate` and `private` were separated; if
>> it were implemented today, it would make sense that `private` would cause
>> `value` to be invisible outside the behavior. Even at the time, a `private`
>> underlying `value` shouldn't be (unless I'm mistaken) visible outside the
>> file.
>>
>> (That said, there is a recurrent theme on this list where people ask for
>> new support for encapsulating members from visibility in ever smaller
>> scopes. I continue to be not in favor of the new `private`, but even
>> allowing its existence, providing all the permutations of
>> type/file/extension visibility is (afaict) and should continue to be (imo)
>> a non-goal. The distinction between public and internal not only avoids
>> pollution in your autocomplete list but also provides important safety
>> wins, and I think those gains are increasingly limited the finer we dice
>> these access levels.)
>>
>> My proposal makes this feature (stored properties inside of computed
>>> properties) significantly easier to implement. For singular or unique cases
>>> of needing this functionality, in lieu of having to learn "Property
>>> Behaviors," its syntax and how to effectively write them, one would only
>>> need to add their properties to their computed properties' declaration
>>> block.
>>>
>>> Thoughts?
>>>
>>
>> I get that your proposal allows you to write one fewer declaration in the
>> case of an ad-hoc behavior than SE-0030 would. That is, in the case of
>> SE-0030, you'd have to declare both a behavior and a member that uses it;
>> in your case, you could declare just the member and implement the behavior
>> there. However, I think I'd want to see some concrete use cases that are
>> better served by this proposal than by SE-0030, which is the more general
>> solution as far as I can tell. In the two use cases you've mentioned, one
>> is served by `didSet`, and the other (`synchronized`) is a reusable
>> behavior for which SE-0030 offers the more elegant solution.
>>
>>
>>
>>> On Fri, Jan 13, 2017 at 12:34 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>>
>>>> There was a proposal for custom behaviors using `@`, which if I recall
>>>> would offer encapsulation along the lines of what you're proposing. It was
>>>> an extensively designed system which was deferred for consideration, but
>>>> which would be in scope again in phase 2. The proposal is in the
>>>> swift-evolution repository--does that address your use case?
>>>> On Fri, Jan 13, 2017 at 11:27 Joseph Newton via swift-evolution <
>>>> swift-evolution at swift.org> wrote:
>>>>
>>>>> Hi,
>>>>>
>>>>> I've done a lot of Objective-C and have been been a fan of Swift since
>>>>> it's come out, however, there's a small feature of Objective-C that I would
>>>>> really like to see implemented in Swift:
>>>>>
>>>>> In Objective-C you have a few options implementation-wise when
>>>>> creating properties for your class. You could use the *@synthesize *statement
>>>>> to have the compiler create the backing ivar along with the appropriate
>>>>> accessor methods or your could use the *@dynamic* statement to tell
>>>>> the compiler that you're going to create your own accessor methods and
>>>>> backing ivar (if needed).
>>>>>
>>>>> Additionally, one could use the *@synthesize* statement to have the
>>>>> compiler create the backing ivar, and then they could create custom
>>>>> accessor methods to supply custom functionality or validation. I use this
>>>>> third case extensively in my Objecitve-C code but there's not a concise way
>>>>> of doing this in Swift.
>>>>>
>>>>> For example, I might have an Objective-C implementation that looks
>>>>> like this:
>>>>>
>>>>> @interface Foo : NSObject
>>>>> @property (nullable, copy) NSString *bar;
>>>>> @end
>>>>>
>>>>> @implementation Foo
>>>>> @synthesize bar = _bar; // Creates ivar '_bar'
>>>>>
>>>>> - (void)setBar:(NSString *)bar {
>>>>>     if (bar.length < 100)
>>>>>         _bar = nil;
>>>>>     else
>>>>>         _bar = [bar copy];
>>>>> }
>>>>>
>>>>> @end
>>>>>
>>>>> Currently, the only way to implement this in Swift - AFAIK - is as
>>>>> follows:
>>>>>
>>>>> class Foo {
>>>>>     private var _bar: String?
>>>>>     public var bar: String? {
>>>>>         get { return _bar }
>>>>>         set {
>>>>>             if (newValue?.characters.count ?? 0) < 100 {
>>>>>                 _bar = nil
>>>>>             }
>>>>>             else {
>>>>>                 _bar = newValue.copy() as! String
>>>>>             }
>>>>>         }
>>>>>     }
>>>>> }
>>>>>
>>>>> Although this works, it isn't exactly glamorous. The main drawback of
>>>>> this implementation (unless intended) is that you now have any additional
>>>>> '_bar' variable accessible within the scope of your class.
>>>>>
>>>>> My proposal is to allow stored properties in the declaration block of
>>>>> computed properties. For this example, the '_bar' declaration would simply
>>>>> be moved inside of the declaration block for 'bar':
>>>>>
>>>>> class Foo {
>>>>>     public var bar: String? {
>>>>>         var _bar: String?
>>>>>
>>>>>         get { return _bar }
>>>>>         set {
>>>>>             if (newValue?.characters.count ?? 0) < 100 {
>>>>>                 _bar = nil
>>>>>             }
>>>>>             else {
>>>>>                 _bar = newValue.copy() as! String
>>>>>             }
>>>>>         }
>>>>>     }
>>>>> }
>>>>>
>>>>> Only the getter and setter methods of 'bar' are allowed to access and
>>>>> modify the stored '_bar' property. My proposal would also allow for
>>>>> multiple stored properties of varying types within the scope of a single
>>>>> computed property. This would also simply atomic synchronization for single
>>>>> variables:
>>>>>
>>>>> class Foo {
>>>>>     static var synchronizedBar: String? {
>>>>>         var queue = DispatchQueue(label: "Foo.synchronizedBar")
>>>>>         var bar: String?
>>>>>
>>>>>         get { return queue.sync { return bar } }
>>>>>         set { queue.sync { bar = newValue } }
>>>>>     }
>>>>> }
>>>>>
>>>>> Are there any suggestions or arguments, for or against, for this
>>>>> proposal?
>>>>>
>>>>> -- Joe Newton
>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution at swift.org
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>
>>>>
>>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170114/551637ab/attachment.html>


More information about the swift-evolution mailing list