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

Joseph Newton jnewto32 at gmail.com
Sat Jan 14 09:36:39 CST 2017


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?

 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 }
    }
}

*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()
            }
        }
    }
}

*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
            }
        }
    }
}

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
    }
}

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/21c4c433/attachment.html>


More information about the swift-evolution mailing list