> On Feb 23, 2017, at 02:19, Matthew Johnson <[email protected]> wrote:
> 
>> 
>> On Feb 22, 2017, at 6:06 PM, Anton Mironov <[email protected] 
>> <mailto:[email protected]>> wrote:
>> 
>> 
>>> On Feb 23, 2017, at 01:18, Matthew Johnson <[email protected] 
>>> <mailto:[email protected]>> wrote:
>>> 
>>>> 
>>>> On Feb 22, 2017, at 5:06 PM, Anton Mironov <[email protected] 
>>>> <mailto:[email protected]>> wrote:
>>>> 
>>>> -1
>>>> I support improvements in this area but I do not think that adding guarded 
>>>> closures will fix the case.
>>>> It raises multiple concerns:
>>>> - prepending ? to the closure declaration is as forgettable as `[weak 
>>>> self]`
>>> 
>>> No, this is why I included the `@guarded` parameter annotation.  This 
>>> allows an API to require its callers to use a guarded closure.  Strong 
>>> references would have to be explicit in the capture list.
>>> 
>>>> - reactive programming often assumes chaining of operations. How guarded 
>>>> closures affect next operations in the chain?
>>> 
>>> Can you provide a concrete example of real world code you wrote manually?  
>>> I will convert it to use guarded closures to show how it is affected.
>> 
>> You can use the second piece of code I’ve provided before.
> 
> How do `map` and `onUpdate` store the reference to the context?  Is it weak?  
> If so, what happens when the context is released?  After you answer that I 
> will be able to show how it would look under this proposal.

Yes, they store a weak reference to the context.

This code implies using primitive that has multiple update events (button 
actions in this case) followed by a single completion event (can only be an 
error of context deallocation in this case). I call it `Channel` (because name 
`Stream` is already taken by `Foundation.Stream` aka `NSStream`).

A release of context will lead to a release of closure and all operations it 
depends on.

`Channel.map`’s context does not exist anymore so closure will be released and 
`Channel` returned from `map` will be immediately completed with failure.
This will also lead to a release of `Channel` returned by `Channel.debounce()`. 
`Channel.debounce()` will lead to release of `Channel` returned by 
`actions(forEvents: [.touchUpInside])`. 

`Channel.onUpdate`’s context does not exist anymore so closure will be released.
This will also lead to a release of `Channel` returned by `Channel.distinct()`.

It might look complex but it is based on a simple idea: release all retained 
resources if your context is gone.

>> 
>>> 
>>>> - the closure must exist until either the control deallocates (source of 
>>>> actions) or self deallocates (destination of actions). Guarded closure 
>>>> will not provide an expected behavior
>>> 
>>> Yes they will.  The guarded closure lives until the control releases it.  
>>> But it becomes a no-op if any of the references captured with a guard are 
>>> released before that happens.  This is much like the behavior of the target 
>>> / action pattern but generalized to support closures.
>> 
>> I doubt that turning closure into no-op is a simple thing to do. It will 
>> require having a registry of closures that depend on an instance. A runtime 
>> will have to go through the registry and turn closures into no-op. Or there 
>> is an another solution that I do not see.
> 
> What I mean when I say no-op is that the code the user places in the closure 
> would be prefixed by a `guard` clause with an early return.  The no-op is 
> when the guard clause is triggered, just as if you had written it manually.

I think that this is an important part. Using no-op (or avoiding execution of 
closure as I understand it) leads to another unwanted retain of variables 
captured by the closure.

> 
> I imagine the compiler might also place an additional check inside the `else` 
> of the `guard` which would release any context it was still hanging on to as 
> it would know that the context was no longer needed.  Imagine this as if the 
> compiler synthesized code had access to an optional strong reference to the 
> context and set it to nil in the else clause.  It would also be possible for 
> this closure to expose an `isAlive: Bool` property that would check whether 
> or not the context was still around.  This is a bit of hand waving - I’m sure 
> the real implementation would be more sophisticated.  But I think that 
> conveys the basic idea.
> 
> Here is some pseudocode:
> 
> let foo: Foo = Foo()
> let bar: Bar = Bar()
> 
> // hand out references to the owner that controls the lifetime of foo and bar
> 
> ?{ 
>          // compiler synthesized code
>          // this is a property on the closure object itself, not visible 
> within the scope of the user code in the closure
>          // it is used by libraries to detect when they can safely discard 
> their reference to the closure because it has become a no-op
>          // context is a super secret compiler reference to the context of 
> the closure
>          var isActive: Bool { return context != nil }
> 
>        // compiler synthesized code that prefixes the user code
>        guard let foo = foo, let bar = bar else {
>            context = nil
>            return
>        }
>        // end compiler synthesized code
>        
>        // begin user code
>        // do something with foo and bar
> }
> 
>> 
>>> 
>>>> - managing lifecycle of nested guarded closures could be complex to 
>>>> understand and implement into the language
>>> 
>>> I’m glad you brought this up.  I’ll give it some thought.  If there does 
>>> turn out to be complexity involved I wouldn’t have a problem prohibiting 
>>> that.
>>> 
>>>> - why would you consider using @escaping instead of @guarded?
>>> 
>>> Because sometimes the right default for a function taking an escaping 
>>> closure is a strong reference.  I wouldn't want `DispatchQueue.async` to 
>>> take a guarded closure.  That API doesn’t contain any semantic content 
>>> around *why* you dispatched async.  It’s not a callback, but instead a way 
>>> of moving work around.
>>> 
>>>> 
>>>> I personally prefer doing something like this:
>>>> 
>>>> ```swift
>>>> self.button.onAction(forEvents: [.touchUpInside], context: self) { (self, 
>>>> sender, event) in
>>>>    self.performSearch(query: self.searchField.text)
>>>> }
>>>> ```
>>>> 
>>>> or
>>>> 
>>>> ```swift
>>>> self.button.actions(forEvents: [.touchUpInside])
>>>>     .debounce(interval: 3.0)
>>>>     .map(context: self) { (self, _) in
>>>>         return self.searchField.text
>>>>     }
>>>>     .distinct()
>>>>     .onUpdate(context: self) { (self, searchQuery) in
>>>>         self.performSearch(query: searchQuery)
>>>>     }
>>>> ```
>>>> 
>>>> This code neither requires an addition of language features nor contains 
>>>> retain cycles. All closures will be released as soon as source or 
>>>> destination deallocates.
>>> 
>>> This isn’t too bad but it does require manually threading the context.  
>>> This is more work for both the library and the client than necessary.  It 
>>> also does not help users avoid an accidental strong reference in the 
>>> closure.  It nudges them not to by offering to thread the context but it 
>>> doesn’t do anything to prevent it.  You can still create a strong reference 
>>> (event to self) without specifying it in the capture list.
>> 
>> You are correct. This will code will not help to avoid accidental strong 
>> reference. But it gives an opportunity to do things without explicit weak 
>> references just as guarded closures do. It also adds an ability to avoid an 
>> execution of pure (or just not bound to context) operations you depends on. 
>> Deallocation of context will lead to cancellation of full chain of 
>> operations and unsubscription from button event.
>> 
>>> I think there is a place for a language solution here.
>> 
>> The only language solution I expect is a static analyzer warning about 
>> retain cycle (like in ObjC).
>> 
>> I’m starting to think that my solution is similar to yours. I’ve done these 
>> things with a library rather than with language support. I will definitely 
>> take advantage of guarded self in closures as soon as the proposal will be 
>> accepted. But I would prefer and suggest using my solution for now.
>> 
>>> 
>>>> 
>>>>> On Feb 22, 2017, at 22:57, Matthew Johnson via swift-evolution 
>>>>> <[email protected] <mailto:[email protected]>> wrote:
>>>>> 
>>>>> Hi David,
>>>>> 
>>>>> I just shared a draft proposal to introduce guarded closures last week: 
>>>>> https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170213/032478.html
>>>>>  
>>>>> <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170213/032478.html>.
>>>>>   I think you would find it very interesting.
>>>>> 
>>>>> I considered including a new capture list specifier `guard` in this 
>>>>> proposal but decided against it.  Guarded behavior requires prefixing the 
>>>>> contents of the closure with a guard clause that returns immediately if 
>>>>> the guard is tripped.  This is a property of the closure as a whole, not 
>>>>> of an individual capture.  For that reason, I decided that allowing a 
>>>>> `guard` specifier for an individual capture would be inappropriate.  
>>>>> 
>>>>> Instead, a guarded closure has a guarded by default capture behavior 
>>>>> which can be overridden with `weak`, `unowned` or `strong` in the capture 
>>>>> list.  The thread on this proposal was relatively brief.  I plan to open 
>>>>> a PR soon after making a few minor modifications.
>>>>> 
>>>>> Matthew
>>>>> 
>>>>>> On Feb 22, 2017, at 2:48 PM, David Hedbor via swift-evolution 
>>>>>> <[email protected] <mailto:[email protected]>> wrote:
>>>>>> 
>>>>>> Hello,
>>>>>> 
>>>>>> (apologies if this got sent twice - gmail and Apple mail seems to 
>>>>>> confused as to what account the first mail was sent from)
>>>>>> 
>>>>>> I’m new to this mailing list, but have read some archived messages, and 
>>>>>> felt that this would be a reasonable subject to discuss. It’s somewhat 
>>>>>> related to the recent posts about @selfsafae/@guarded but distinctly 
>>>>>> different regardless.
>>>>>> 
>>>>>> 
>>>>>> Problem:
>>>>>> 
>>>>>> It’s often desirable not to capture self in closures, but the syntax for 
>>>>>> doing so adds significant boilerplate code for [weak self] or us unsafe 
>>>>>> when used with [unowned self]. Typically you’d do something like this:
>>>>>> 
>>>>>>   { [weak self] in    self?.execute() }
>>>>>> 
>>>>>> This is simple enough but often doesn’t work:
>>>>>> 
>>>>>> { [weak self] in self?.boolean = self?.calculateBoolean() ]
>>>>>> 
>>>>>> This fails because boolean is not an optional. This in turn leads to 
>>>>>> code like this:
>>>>>> 
>>>>>> { [weak self] in
>>>>>>    guard let strongSelf = self else { return }
>>>>>>    strongSelf.boolean = self.calculateBoolean()  }
>>>>>> 
>>>>>> And this is the boilerplate code. My suggestion is to add a syntax that 
>>>>>> works the same as the third syntax, yet doesn’t require the boilerplate 
>>>>>> code.
>>>>>> 
>>>>>> 
>>>>>> Solution:
>>>>>> 
>>>>>> Instead of using unowned or weak, let’s use guard/guarded syntax:
>>>>>> 
>>>>>> 
>>>>>> { [guard self] in
>>>>>>    self.isExecuted = self.onlyIfWeakSelfWasCaptured()
>>>>>> }
>>>>>> 
>>>>>> In essence, guarded self is equivalent to a weak self, that’s captured 
>>>>>> when the closure is executed. If it was already released at that point, 
>>>>>> the closure is simply not executed. It’s equivalent to:
>>>>>> 
>>>>>> { [weak self] in
>>>>>>    guard let strongSelf = self else { return }
>>>>>>    strongSelf.isExecuted = strongSelf.onlyIfWeakSelfWasCaptured()
>>>>>> }
>>>>>> 
>>>>>> Except with a lot less boilerplate code, while not losing any clarify in 
>>>>>> what it does.
>>>>>> 
>>>>>> Impact / compatibility:
>>>>>> 
>>>>>> This is simply additive syntax, and wouldn’t affect any existing code.
>>>>>> _______________________________________________
>>>>>> swift-evolution mailing list
>>>>>> [email protected] <mailto:[email protected]>
>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>>> 
>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> [email protected] <mailto:[email protected]>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to