[swift-evolution] Guaranteed closure execution

michael.peternell at gmx.at michael.peternell at gmx.at
Sat Apr 23 08:47:49 CDT 2016


Hi,

> Am 23.04.2016 um 14:56 schrieb Gwendal Roué via swift-evolution <swift-evolution at swift.org>:
> 
> Thanks for your input Michael,
> 
> You're right that argument modifiers should go before the argument, and type qualifiers before the type:
> 
> 	func f(@once closure: @noescape () -> ())
> 
> Since @once actually implies @noescape (as described earlier in the thread), it can be shortened to:
> 
> 	func f(@once closure: () -> ())	// implicit @noescape

why not just write

    func f(closure: @once () -> ()) // ?

Because, to be honest, I don't want to have to learn the difference between an "argument qualifier" and a "type attribute". They are both just words to me. Internally, I have two conflicting definitions of them: 1) "argument qualifier" = "what is left of the name", "type attribute" = "what comes right after the name, but before the type" and 2) don't wanna say it because it's even more wrong. What I wanna say: the average user will not care about that distinction, he will not want to understand it, and he certainly will not want to have to understand it. And putting everything (however it is called), after the name, looks much nicer IMHO. I also think that this is one of the reason why SE-0049 was accepted. Furthermore, I don't think my definition 1) is too wrong, because I think the implementation of SE-0049 is a syntax-only change, right?

> 
>> Or maybe it's just because I can't think of a realistic use case for using "@noescape(once)".
> 
> The use case is the following: @once closures let the compiler know that the closure code is executed. It allows declaration of undefined `let` variables that are initialized in the @once closure:
> 
> 	let x: Int
> 	f { x = 1 }
> 	// use x
> 
> Without @once, the developer has to work around:
> 
> 	var x: Int = 0 // two problems: `var` declaration, and dummy value
> 	f { x = 1 }
> 	// use x
> 
> If this proposal would be accepted, the next step would be to ask for dispatch_sync functions et al. to use this qualifier (as well as other standard functions like autoreleasepool, withUnsafeBufferPointer, etc.):
> 
> 	let x: Int
> 	dispatch_sync(queue) {
> 		x = 1
> 	}
> 
> And now high-level objects that use internal queues are easier to use:
> 
> 	let items: [Item]
> 	let users: [User]
> 	dbQueue.inDatabase { db in
> 		items = Item.fetchAll(db)
> 		users = User.fetchAll(db)
> 	}

These are all nice examples. If @once can be combined with the let/var-initialization system, it's a real improvement. I think dispatch_sync should be defined like

    func dispatch_sync(queue: dispatch_queue_t, block: @once () throws -> ()) rethrows { ... }

and since @once implies @noescape, @noescape would be optional. The following would be equivalent:

    func dispatch_sync(queue: dispatch_queue_t, block: @once @noescape () throws -> ()) rethrows { ... }

I think that our opinions only differ regarding the proper syntax of these features...

Another feature I would like to have is converting a block into a noescape block. This seems necessary when creating wrappers around existing APIs. E.g.

    func swift_dispatch_sync(queue: dispatch_queue_t, block: @noescape! () -> ()) {
        dispatch_sync(queue, block: block)
    }

(note the "!" after "@noescape".)
This should behave like this:

    func swift_dispatch_sync(queue: dispatch_queue_t, block: @noescape () -> ()) {
        var isAlive = true
        let noescapeBlock = {
            if(!isAlive) { fatalError("noescape block called out of scope") }
            block()
        }
        defer { isAlive = false }
        dispatch_sync(queue, block: noescapeBlock)
    }

I would like to tell the compiler: "I see that you cannot prove at compile time, that this closure doesn't escape, but believe me when I say, 'it really really does not escape'. Can you please be less skeptical? May the fatalError hit me if I'm wrong." (And the same would apply to '@once', or '@noescape(once)'.)

When I think of it, my experimental synchronized-function would also profit from '@once':

    /// same as Objective-C's @synchronized(object) { ... } function.
    func synchronized<T>(lock: AnyObject, @noescape _ closure: () throws -> T) rethrows -> T {
        var result: T;
        objc_sync_enter(lock)
        defer { objc_sync_exit(lock) }
        result = try closure()
        return result;
    }

-Michael

> 
> This pattern would greatly improve many libraries, and my own http://github.com/groue/GRDB.swift in particular.
> 
> Gwendal Roué
> 
> 
>> Le 23 avr. 2016 à 14:16, Michael Peternell <michael.peternell at gmx.at> a écrit :
>> 
>> Hi Gwendal,
>> 
>>> Am 23.04.2016 um 12:18 schrieb Gwendal Roué via swift-evolution <swift-evolution at swift.org>:
>>> 
>>> Hello Andrew,
>>> 
>>> I'm rather embarrassed: the initial design of this proposal was based on a modifier of @noescape:
>>> 
>>> 	func f(@noescape(once) closure: () -> ()) { … }
>>> 
>>> But since the 0049 proposal has been accepted (https://github.com/apple/swift-evolution/blob/master/proposals/0049-noescape-autoclosure-type-attrs.md), @noescape is no longer an argument qualifier, but a type attribute.
>> 
>> maybe I cannot give competent advice from the language designers perspective, but I can speak as a language user.
>> 
>> I think it is more intuitive to have the name of the argument first: "ARGUMENT-NAME: NOESCAPE AUTOCLOSURE TYPE FOOBAR-QUALIFIER..., NEXT-ARGUMENT-NAME..." and not "SOME-MODIFIERS-FIRST ARGUMENT-NAME: SOME-MODIFIERS-LAST". I think the old behavior reminded me too much of C, where the argument name is at some arbitrary place, surrounded by type names, parens, and whatnot; "void foo(int __cdecl (*bar)(int foobar, void *context))" where "bar" is the argument name, the first "int" is the return type of the "bar" function, "int" and "void*" are the two argument types, and "__cdecl" is the calling convention of the "bar" function.
>> 
>> And about @noescape(once)... Would it not work anymore? I know it's not part of the current SE-0049 proposal, but I can't see why it couldn't be expanded to include `once`. Or maybe it's just because I can't think of a realistic use case for using "@noescape(once)".
>> 
>>> The `once` discussed here can not be part of the type: if noescape can understandably be part of the type, the fact that a function guarantees it will call a closure once is a quality of that function, not of the closure.
>> 
>> Yes it is a quality of the function and not of the type. But why not just leave it at that place and just call it "argument qualifier"? Or maybe the general layout changed? In Swift 2 it was "ARGUMENT_QUALIFIERS ARGUMENT_NAME: TYPE_ATTRIBUTES TYPE", and in Swift 3 it is "ARGUMENT_NAME: ARGUMENT_QUALIFIERS TYPE_ATTRIBUTES TYPE"?
>> 
>>> So the proposed @noescape(once) syntax is now confusing as it would mix a type attribute and a argument qualifier.
>>> 
>>> I don't quite know how to escape this:
>>> 
>>> 	// two @ signs
>>> 	func f(@noescape @once closure: () -> ()) { … }
>>> 
>>> 	// Implies @noescape
>>> 	func f(@once closure: () -> ()) { … }
>> 
>> How about
>> 
>>   func f(closure: @noescape(once) () -> ()) { ... }
>> 
>> -Michael
>> 
>>> 
>>> I'd like advice from competent people before I would attempt a rewrite of the proposal.
>>> 
>>> Gwendal Roué
>>> 
>>>> Le 10 avr. 2016 à 23:26, Andrew Bennett <cacoyi at gmail.com> a écrit :
>>>> 
>>>> Sorry I missed that scrolling back through the history, that proposal looks great. It doesn't look like it has been submitted as a pull request to swift-evolution yet though.
>>>> 
>>>> On Sunday, 10 April 2016, Gwendal Roué <gwendal.roue at gmail.com> wrote:
>>>> Felix Cloutier already wrote one: https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md
>>>> 
>>>> Do you think it needs to be rewritten?
>>>> 
>>>> Gwendal Roué
>>>> 
>>>>> Le 10 avr. 2016 à 14:56, Andrew Bennett <cacoyi at gmail.com> a écrit :
>>>>> 
>>>>> Hi, not beyond this thread that I have seen. I think it's worth you summarizing this thread in a formal proposal and putting it up for discussion or submitting it as a PR :)
>>>>> 
>>>>> On Sunday, 10 April 2016, Gwendal Roué <swift-evolution at swift.org> wrote:
>>>>> Hello all,
>>>>> 
>>>>> I was wondering if this topic had evolved in anyway since its original introduction.
>>>>> 
>>>>> @noescape(once) would still be a useful addition to the language!
>>>>> 
>>>>> Gwendal Roué
>>>>> 
>>>>> 
>>>>> 
>>>>>> Le 3 févr. 2016 à 22:21, Félix Cloutier via swift-evolution <swift-evolution at swift.org> a écrit :
>>>>>> 
>>>>>> I updated the proposal to address some concerns. It can be found at: https://github.com/zneak/swift-evolution/blob/master/proposals/00xx-noescape-once.md
>>>>>> 
>>>>>> Things that changed:
>>>>>> 
>>>>>> 	• It now says that the closure must be called on code paths where the function throws;
>>>>>> 	• you can have multiple @noescape(once) parameters but they can't make assumptions from one another.
>>>>>> 
>>>>>> I'm not 100% convinced that forcing a call on code paths that throw is always desirable. I've changed it because Chris's support probably means that the feature has better chances of making it, but I'm not convinced yet. If throwing allows me to return without calling the closure, I can write this:
>>>>>> 
>>>>>> do {
>>>>>> 	let foo: Int
>>>>>> 	try withLock(someLock, timeout: 0.5) {
>>>>>> 		foo = sharedThing.foo
>>>>>> 	}
>>>>>> } catch {
>>>>>> 	print("couldn't acquire lock fast enough")
>>>>>> }
>>>>>> 
>>>>>> which would be kind of messy if instead, the closure needed a parameter to tell whether the lock was acquired or not when it runs.
>>>>>> 
>>>>>> Félix
>>>>>> 



More information about the swift-evolution mailing list