[Pitch] Improving capturing semantics of local functions

Hello evolution folks,

After the positive feedback on the idea of improving capturing semantics of local functions, Alex Lynch and I worked on a proposal. Please let us know if you have any feedback:

We’re also looking for help in implementing it. Please let us know!

David.

Imho it’s good to copy the concept of capture lists — but it could be done differently, without polluting the body:
func foo[capture list](parameters) {}
I’m not sure if there are corner cases with subscript-syntax, but in general, I think this approach is nicer.

- Tino

Hello evolution folks,

After the positive feedback on the idea of improving capturing semantics of local functions, Alex Lynch and I worked on a proposal. Please let us know if you have any feedback:

https://github.com/hartbit/swift-evolution/blob/improving-capturing-semantics-of-local-functions/proposals/XXXX-improve-capture-semantics-of-local-functions.md

So, quoting the proposal:

First of all, this proposal suggests extending the requirement of the self. prefix to local functions, but only if the local function is used as or used inside an escaping closure.

I don't love that the use of a function many lines away can cause errors in that closure. There's a "spooky action-at-a-distance" quality to this behavior that I don't like.

The best idea I have is to require local functions to be annotated with `@escaping` if they're to be used in an escaping closure:

    func foo() {
        // `local1` is nonescaping since it isn't marked with the @escaping attribute.
        func local1() {
            bar()
        }
        local1() // OK, direct call
        { local1() }() // OK, closure is nonescaping
        DispatchQueue.main.async(execute: local1) // error: passing non-escaping function 'local2' to function expecting an @escaping closure
        DispatchQueue.main.async { local1() } // error: closure use of non-escaping function 'local2' may allow it to escape

        @escaping func local2() {
            bar() // error: call to method 'bar' in escaping local function requires explicit 'self.' to make capture semantics explicit
        }

        @escaping func local3() {
           self. bar() // OK, explicit `self`
        }
        DispatchQueue.main.async(execute: local3) // OK, escaping function
        DispatchQueue.main.async { local3() } // OK, escaping closure
   }

    func bar() {
        print("bar")
    }

But this would be quite source-breaking. (Maybe it could be introduced as a warning first?)

Secondly, this proposal suggests allowing the same capture list syntax from closures in local functions. Capture lists would still be invalid in top-level and member functions.

I think this is a good idea, but I don't like bringing the already weird use of `in` to actual functions.

By analogy with the current closure syntax, the capture list ought to go somewhere before the parameter list, in one of these slots:

1. func fn<T>[foo, bar](param: T) throws -> T where T: Equatable { … }
2. func fn[foo, bar]<T>(param: T) throws -> T where T: Equatable { … }
3. func [foo, bar] fn<T>(param: T) throws -> T where T: Equatable { … }
4. [foo, bar] func fn<T>(param: T) throws -> T where T: Equatable { … }

Of these options, I actually think #4 reads best; 1 and 2 are very cluttered, and 3 just seems weird. But it seems like the one that would be easiest to misparse.

···

On Nov 12, 2017, at 12:55 AM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

I'd vote for # 1

Starting with "func" helps signal that the context is a function. (Though I know some things can precede "func".)

Having the function name second is important to me: you don't have to scan further for its name.

Template arguments seem more important than capture lists, so I'd put it before the other.

···

--
C. Keith Ray

* What Every Programmer Needs To… by C. Keith Ray [PDF/iPad/Kindle] <- buy my book?
* http://www.thirdfoundationsw.com/keith_ray_resume_2014_long.pdf
* http://agilesolutionspace.blogspot.com/

On Nov 12, 2017, at 8:11 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

1. func fn<T>[foo, bar](param: T) throws -> T where T: Equatable { … }
2. func fn[foo, bar]<T>(param: T) throws -> T where T: Equatable { … }
3. func [foo, bar] fn<T>(param: T) throws -> T where T: Equatable { … }
4. [foo, bar] func fn<T>(param: T) throws -> T where T: Equatable { … }

Hello evolution folks,

After the positive feedback on the idea of improving capturing semantics of local functions, Alex Lynch and I worked on a proposal. Please let us know if you have any feedback:

https://github.com/hartbit/swift-evolution/blob/improving-capturing-semantics-of-local-functions/proposals/XXXX-improve-capture-semantics-of-local-functions.md

So, quoting the proposal:

First of all, this proposal suggests extending the requirement of the self. prefix to local functions, but only if the local function is used as or used inside an escaping closure.

I don't love that the use of a function many lines away can cause errors in that closure. There's a "spooky action-at-a-distance" quality to this behavior that I don't like.

Agreed.

I think this is a good idea, but I don't like bringing the already weird use of `in` to actual functions.

By analogy with the current closure syntax, the capture list ought to go somewhere before the parameter list, in one of these slots:

1. func fn<T>[foo, bar](param: T) throws -> T where T: Equatable { … }
2. func fn[foo, bar]<T>(param: T) throws -> T where T: Equatable { … }
3. func [foo, bar] fn<T>(param: T) throws -> T where T: Equatable { … }
4. [foo, bar] func fn<T>(param: T) throws -> T where T: Equatable { … }

Of these options, I actually think #4 reads best; 1 and 2 are very cluttered, and 3 just seems weird. But it seems like the one that would be easiest to misparse.

Another option that reads nicely IMHO is

func fn<T>(param: T) throws -> T where T : Equatable [foo, bar] { … }

I think #4 is ambiguous with array literals unfortunately.

Perhaps this proposal should be split in two — the ‘self.’/escaping part is source breaking, and will likely require more discussion. Adding capture lists to local functions seems like a more straightforward change.

Slava

···

On Nov 12, 2017, at 8:11 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 12, 2017, at 12:55 AM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Hello evolution folks,

After the positive feedback on the idea of improving capturing semantics of local functions, Alex Lynch and I worked on a proposal. Please let us know if you have any feedback:

https://github.com/hartbit/swift-evolution/blob/improving-capturing-semantics-of-local-functions/proposals/XXXX-improve-capture-semantics-of-local-functions.md

So, quoting the proposal:

First of all, this proposal suggests extending the requirement of the self. prefix to local functions, but only if the local function is used as or used inside an escaping closure.

I don't love that the use of a function many lines away can cause errors in that closure. There's a "spooky action-at-a-distance" quality to this behavior that I don't like.

The best idea I have is to require local functions to be annotated with `@escaping` if they're to be used in an escaping closure:

   func foo() {
       // `local1` is nonescaping since it isn't marked with the @escaping attribute.
       func local1() {
           bar()
       }
       local1() // OK, direct call
       { local1() }() // OK, closure is nonescaping
       DispatchQueue.main.async(execute: local1) // error: passing non-escaping function 'local2' to function expecting an @escaping closure
       DispatchQueue.main.async { local1() } // error: closure use of non-escaping function 'local2' may allow it to escape

       @escaping func local2() {
           bar() // error: call to method 'bar' in escaping local function requires explicit 'self.' to make capture semantics explicit
       }

       @escaping func local3() {
          self. bar() // OK, explicit `self`
       }
       DispatchQueue.main.async(execute: local3) // OK, escaping function
       DispatchQueue.main.async { local3() } // OK, escaping closure
  }

   func bar() {
       print("bar")
   }

But this would be quite source-breaking. (Maybe it could be introduced as a warning first?)

I like the idea of requiring @escaping to be explicit on local funcs, but I'm worried that it might be too onerous; after all, we infer @escapingness on closures quite successfully. At the same time, I agree that applying semantic rules based on how the func is used, potentially much later in the function, is really spooky. I don't have a great alternative right now.

Random note: we currently infer non-escapingness for local funcs that capture ``inout``s, since those cannot be allowed to escape. In fact, this is the only way to make a local function non-escaping at all.

John.

···

On Nov 12, 2017, at 11:11 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 12, 2017, at 12:55 AM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

Secondly, this proposal suggests allowing the same capture list syntax from closures in local functions. Capture lists would still be invalid in top-level and member functions.

I think this is a good idea, but I don't like bringing the already weird use of `in` to actual functions.

By analogy with the current closure syntax, the capture list ought to go somewhere before the parameter list, in one of these slots:

1. func fn<T>[foo, bar](param: T) throws -> T where T: Equatable { … }
2. func fn[foo, bar]<T>(param: T) throws -> T where T: Equatable { … }
3. func [foo, bar] fn<T>(param: T) throws -> T where T: Equatable { … }
4. [foo, bar] func fn<T>(param: T) throws -> T where T: Equatable { … }

Of these options, I actually think #4 reads best; 1 and 2 are very cluttered, and 3 just seems weird. But it seems like the one that would be easiest to misparse.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

This is relatively rare, so I’d suggest introducing a context sensitive keyword to make it explicit, perhaps:

5. func fn<T>[foo, bar](param: T) throws -> T where T: Equatable captures [foo, bar] { … }

It makes sense (IMO) to keep it near the body of the function, since it is more an artifact of the implementation than it is about the API. Yes I know that caring about the API of a local function is weird :-)

-Chris

···

On Nov 12, 2017, at 8:11 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

Secondly, this proposal suggests allowing the same capture list syntax from closures in local functions. Capture lists would still be invalid in top-level and member functions.

I think this is a good idea, but I don't like bringing the already weird use of `in` to actual functions.

By analogy with the current closure syntax, the capture list ought to go somewhere before the parameter list, in one of these slots:

1. func fn<T>[foo, bar](param: T) throws -> T where T: Equatable { … }
2. func fn[foo, bar]<T>(param: T) throws -> T where T: Equatable { … }
3. func [foo, bar] fn<T>(param: T) throws -> T where T: Equatable { … }
4. [foo, bar] func fn<T>(param: T) throws -> T where T: Equatable { … }

Of these options, I actually think #4 reads best; 1 and 2 are very cluttered, and 3 just seems weird. But it seems like the one that would be easiest to misparse.

Hello evolution folks,

After the positive feedback on the idea of improving capturing semantics of local functions, Alex Lynch and I worked on a proposal. Please let us know if you have any feedback:

https://github.com/hartbit/swift-evolution/blob/improving-capturing-semantics-of-local-functions/proposals/XXXX-improve-capture-semantics-of-local-functions.md

So, quoting the proposal:

First of all, this proposal suggests extending the requirement of the self. prefix to local functions, but only if the local function is used as or used inside an escaping closure.

I don't love that the use of a function many lines away can cause errors in that closure. There's a "spooky action-at-a-distance" quality to this behavior that I don't like.

I could go either way on that. Especially since if you want to find out where the escaping references are, you could just remove the @escaping annotation (below) and look for the compiler errors.

The best idea I have is to require local functions to be annotated with `@escaping` if they're to be used in an escaping closure:

   func foo() {
       // `local1` is nonescaping since it isn't marked with the @escaping attribute.
       func local1() {
           bar()
       }
       local1() // OK, direct call
       { local1() }() // OK, closure is nonescaping
       DispatchQueue.main.async(execute: local1) // error: passing non-escaping function 'local2' to function expecting an @escaping closure
       DispatchQueue.main.async { local1() } // error: closure use of non-escaping function 'local2' may allow it to escape

       @escaping func local2() {
           bar() // error: call to method 'bar' in escaping local function requires explicit 'self.' to make capture semantics explicit
       }

       @escaping func local3() {
          self. bar() // OK, explicit `self`
       }
       DispatchQueue.main.async(execute: local3) // OK, escaping function
       DispatchQueue.main.async { local3() } // OK, escaping closure
  }

   func bar() {
       print("bar")
   }

But this would be quite source-breaking. (Maybe it could be introduced as a warning first?)

+1

Secondly, this proposal suggests allowing the same capture list syntax from closures in local functions. Capture lists would still be invalid in top-level and member functions.

I think this is a good idea, but I don't like bringing the already weird use of `in` to actual functions.

By analogy with the current closure syntax, the capture list ought to go somewhere before the parameter list, in one of these slots:

1. func fn<T>[foo, bar](param: T) throws -> T where T: Equatable { … }
2. func fn[foo, bar]<T>(param: T) throws -> T where T: Equatable { … }
3. func [foo, bar] fn<T>(param: T) throws -> T where T: Equatable { … }
4. [foo, bar] func fn<T>(param: T) throws -> T where T: Equatable { … }

Of these options, I actually think #4 reads best; 1 and 2 are very cluttered, and 3 just seems weird. But it seems like the one that would be easiest to misparse.

We should keep capture lists where they are (in the body). Currently functions and closures have very similar syntax:

func doSomething(section: Int, value: Double) -> String { … }

var doSomething: (_ section: Int, _ value: Double) -> String { [captures…] s,v in }

So you basically need to replace “func” with “var”, copy your parameter names in to the body and replace them with underscores (because closure types can’t have argument labels — I think the core team do want to add those add some point IIRC… ). I don’t see a good reason to force developers to also shuffle their capture lists around. When you’re working with local functions, it’s hard enough to style your code so that you keep within your column-limit. It can be delicate, is what I’m saying.

- Karl

···

On 13. Nov 2017, at 05:11, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 12, 2017, at 12:55 AM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Another option that reads nicely IMHO is

func fn<T>(param: T) throws -> T where T : Equatable [foo, bar] { … }

I changed my mind. Putting the capture list just before { } is where I'd rather see it.

···

I think #4 is ambiguous with array literals unfortunately.

Perhaps this proposal should be split in two — the ‘self.’/escaping part is source breaking, and will likely require more discussion. Adding capture lists to local functions seems like a more straightforward change.

Slava

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Hello evolution folks,

After the positive feedback on the idea of improving capturing semantics of local functions, Alex Lynch and I worked on a proposal. Please let us know if you have any feedback:

https://github.com/hartbit/swift-evolution/blob/improving-capturing-semantics-of-local-functions/proposals/XXXX-improve-capture-semantics-of-local-functions.md

So, quoting the proposal:

First of all, this proposal suggests extending the requirement of the self. prefix to local functions, but only if the local function is used as or used inside an escaping closure.

I don't love that the use of a function many lines away can cause errors in that closure. There's a "spooky action-at-a-distance" quality to this behavior that I don't like.

Agreed.

Thanks Brent, Slava and John for your feedback. I think you are right: I picked up the idea from the original thread because it would reduce source breakage, but I’m starting to thing its just not worth it.

Concerning the alternative solution of annotating local functions with @escaping, it’s definitely an interesting idea. It might be slightly more source-breaking but, as mentioned in the proposal, the breakage will attract attention to code where its easy to unknowingly create memory leaks.

I think this is a good idea, but I don't like bringing the already weird use of `in` to actual functions.

By analogy with the current closure syntax, the capture list ought to go somewhere before the parameter list, in one of these slots:

1. func fn<T>[foo, bar](param: T) throws -> T where T: Equatable { … }
2. func fn[foo, bar]<T>(param: T) throws -> T where T: Equatable { … }
3. func [foo, bar] fn<T>(param: T) throws -> T where T: Equatable { … }
4. [foo, bar] func fn<T>(param: T) throws -> T where T: Equatable { … }

Of these options, I actually think #4 reads best; 1 and 2 are very cluttered, and 3 just seems weird. But it seems like the one that would be easiest to misparse.

Another option that reads nicely IMHO is

func fn<T>(param: T) throws -> T where T : Equatable [foo, bar] { … }

I think #4 is ambiguous with array literals unfortunately.

This is pure aesthetics, but I’m a bit bothered by all 5 options. Local functions and closures are syntactically different enough that we can either choose to keep capture lists in the same position relative to the curly braces (like I proposed), or relative to the parameters (like Brent proposes). Don’t you think that the suggestion in the pitch is the least confusing for users? On a side note, Slava’s 5th option seems like its breaking relative position with the curly braces and the parameters.

Perhaps this proposal should be split in two — the ‘self.’/escaping part is source breaking, and will likely require more discussion. Adding capture lists to local functions seems like a more straightforward change.

Seems like a good idea. Once we all come close to an agreement, Alex and I can spit them into two proposals.

David.

···

On 13 Nov 2017, at 05:52, Slava Pestov <spestov@apple.com> wrote:

On Nov 12, 2017, at 8:11 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 12, 2017, at 12:55 AM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

Slava

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

func fn<T>(param: T) throws -> T where T : Equatable [foo, bar] { … }

+1, potentially adding a context sensitive keyword like “capturing” before it.

I think #4 is ambiguous with array literals unfortunately.

Perhaps this proposal should be split in two — the ‘self.’/escaping part is source breaking, and will likely require more discussion. Adding capture lists to local functions seems like a more straightforward change.

+1.

-Chris

···

On Nov 12, 2017, at 8:52 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

5. func fn<T>[foo, bar](param: T) throws -> T where T: Equatable captures [foo, bar] { … }

I guess it can be considered good practice to start a line with the most important information, and move the details to the far right, so that they don’t distract the hasty reader.
I’m not sure if the capture list an example for this, but for good or worse, it didn’t draw much attention in another position either (the first „[foo, bar]“ that slipped through).

That variant (func fn<T>[foo, bar](param: T)) is definitely my favorite, because it keeps a characteristic of closures (first the capture list, then the parameters), but doesn’t carry over the mingling of body and parameters that has to be done in closures.

I think it would be unfortunate to have the capture list before the template parameters:
Template parameters might be used in the capture list, and although Swift doesn’t rely on strict „linearity“ or forward declarations, I don’t want to be forced to use things that aren’t declared yet.

func fn<T>(param: T) throws -> T where T : Equatable [foo, bar] { … }

+1, potentially adding a context sensitive keyword like “capturing” before it.

+1 including the keyword "capturing"

-Thorsten

···

Am 21.11.2017 um 04:00 schrieb Chris Lattner via swift-evolution <swift-evolution@swift.org>:

On Nov 12, 2017, at 8:52 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

I think #4 is ambiguous with array literals unfortunately.

Perhaps this proposal should be split in two — the ‘self.’/escaping part is source breaking, and will likely require more discussion. Adding capture lists to local functions seems like a more straightforward change.

+1.

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution