Should we relax restriction on closing over outer scope in class declarations?

Maybe you could show me what difference you'd expect with classes by
comparing to this code that uses functions? Sorry for being slow here.

The following code prints
W-002 25 W-002 25

// https://swiftlang.ng.bluemix.net/#/repl
// Swift Ver. 3.0.2 (Release)
// Platform: Linux (x86_64)

class WidgetStore {
  var widget: String = "W-001"
  var count: Int = 1;

  func getWidgetProvider() -> ()->String {
      func provideWidget() -> String {
        return widget
      }

    return provideWidget
  }

  func getCountProvider() -> ()->Int {
      func provideCount() -> Int {
        return count
      }

    return provideCount
  }
}

var store = WidgetStore()
var getWidget = store.getWidgetProvider()
var getCount = store.getCountProvider()

store.widget = "W-002"
store.count = 25;

print(store.widget)
print(store.count)

print(getWidget())
print(getCount())

···

On Sat, Dec 24, 2016 at 12:39 PM, Robert Widmann <devteam.codafi@gmail.com> wrote:

The point here is that the escape of the inner value necessitates the
(implicit) escape of the outer captured value. That's something closures
don't have to deal with.

~Robert Widmann

2016/12/24 13:13、Callionica (Swift) <swift-callionica@callionica.com>
のメッセージ:

I may be missing your point about lifetime extension problems. How would
lifetime extension be different for captures by classes than for captures
by function closures?

On Sat, Dec 24, 2016 at 12:00 PM Robert Widmann <devteam.codafi@gmail.com> > wrote:

That still doesn't answer the lifetime extension problems. What is being
described here is a complex extension to the lifetime of memory that is
only being kept alive because of these implicit captures. It sounds like
what you want is a member, and I'm not sure it's worth saving 5 lines to
avoid that fact.

~Robert Widmann

2016/12/24 11:45、Callionica (Swift) via swift-evolution <
swift-evolution@swift.org> のメッセージ:

Missed swift-evol in my reply

---------- Forwarded message ---------
From: Callionica (Swift) <swift-callionica@callionica.com>
Date: Sat, Dec 24, 2016 at 9:44 AM
Subject: Re: [swift-evolution] Should we relax restriction on closing
over outer scope in class declarations?
To: Robert Widmann <devteam.codafi@gmail.com>

These are good questions.

I believe Swift closures currently do implicit self-capture. I wouldn't
expect that behavior to be different when capturing for classes.

If a class needs to refer to an outer self, I would expect the user to
have to give the outer self a new name by assigning to a local. Same deal
for referring to members in an outer class where there are members of the
same name in the inner class: assign outer self to a local and refer to
members through that local.

On Fri, Dec 23, 2016 at 10:16 AM, Robert Widmann < >> devteam.codafi@gmail.com> wrote:

On Dec 23, 2016, at 1:10 AM, Callionica (Swift) via swift-evolution < >> swift-evolution@swift.org> wrote:

I'm certainly assuming that users have a basic understanding of scope
(without that, what would be the intention behind defining the class within
the function?).

Despite its simplicity to you (given the example you’ve cited) this
feature is quite an advanced extension to the current scoping rules.

I'm not clear on what you see as the downside of letting classes capture
locals for users that are unaware of capturing.

I’ll be blunt: This opens a gaping hole in the semantics of variable
lifetimes and potentially introduces an ambiguity into resolution of the
`self` keyword. For the former, consider a light extension to your
original example that introduces a nested class:

class Outer {
  let widgetString: String = createWidgetString()

  // Initializer suppressed
  func foo() -> WidgetStringProvider {
    class SimpleProvider: WidgetStringProvider {
      // Initializer suppressed
      func provideWidgetString() -> String {
        return widgetString
      }
    }
    return SimpleProvider()
  }
}

Now, from SIL’s point of view, widgetString must escape the scope it’s
in and its lifetime should be extended beyond this block. Fine. But what
about Outer? Because we’re returning a reference to SimpleProvider which
escapes the scope of foo(), and instance members of a SimpleProvider
capture outer context, this means we also have to keep Outer alive and
around for destruction (perhaps at the point where SimpleProvider is
destroyed?)

For the latter consider trying to explicitly reference widgetString
here. In provideWidgetString() we close over self in both foo() and
provideWidgetString(). So, if I wish to make reference to Outer’s self explicitly,
how would I go about doing it? Given that today a nested aggregate can
have members with the same identifiers as their parent, how do we know if
we’re capturing them or not when they’re referenced in the inner aggregate?

~Robert Widmann

On Thu, Dec 22, 2016 at 11:26 PM Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Only if you're also assuming that people defining classes within
functions would know about capturing. This violates the principle of
progressive disclosure, since people naturally learn about functions and
classes before they learn about closures and capturing.

On Fri, Dec 23, 2016 at 01:51 Callionica (Swift) < >> swift-callionica@callionica.com> wrote:

Assuming capture were allowed, people defining classes within functions
who didn't want them to capture could position the class definition prior
to any other code in the function so that there would be nothing to
capture.

On Thu, Dec 22, 2016 at 4:13 PM Xiaodi Wu via swift-evolution < >> swift-evolution@swift.org> wrote:

I have to agree with Michael; it seems dangerous to allow implicit
capture by classes. A primary purpose (telos?) of closures is to provide
this functionality, which is actually quite an advanced concept. One knows
to think carefully about this when encountering a closure expression. A
primary purpose of classes is to provide for encapsulation of code.
Accidentally extending the lifetime of a local variable in a containing
scope would be hard to notice and highly unexpected functionality. Better
not to mix these things.

On Thu, Dec 22, 2016 at 17:54 Micah Hainline via swift-evolution < >> swift-evolution@swift.org> wrote:

That's exactly what I'm suggesting, the class declaration could work
similarly to a closure.

> On Dec 22, 2016, at 4:15 PM, Michael Ilseman <milseman@apple.com> >> wrote:

>

> Are you asking for a class declaration to implicitly capture and extend
the lifetime of local variables? That seems like something that’s better
done explicitly. Perhaps it’s better to think about how to reduce the
boiler plate code, e.g. better default initializers.

>

> (this is of course, additive and beyond the current scope of Swift 4
phase 1 planning)

>

>> On Dec 22, 2016, at 2:39 PM, Micah Hainline via swift-evolution < >> swift-evolution@swift.org> wrote:

>>

>> Currently we allow declarations of a new class in local scope, but

>> nothing can be referenced from the outer scope. Thus this is illegal:

>>

>> ```

>> func foo() {

>> let widgetString: String = createWidgetString()

>> class SimpleProvider: WidgetStringProvider {

>> func provideWidgetString() -> String {

>> return widgetString // Illegal, defined in outer scope

>> }

>> }

>> doThingsWithWidget(provider: WidgetStringProvider())

>> }

>> ```

>>

>> I'm trying to feel out the edges of this decision, and figure out why

>> exactly this isn't allowed now, and how we might want to relax this in

>> the future if possible. While not a common construct, it is a useful

>> one.

>>

>> Obviously there are ways around it, very simple to create an init and

>> a private let and do something like this:

>>

>> ```

>> func foo() {

>> let widgetString: String = createWidgetString()

>> class SimpleProvider: WidgetStringProvider {

>> private let widgetString: String

>>

>> init(widgetString: String) {

>> self.widgetString = widgetString

>> }

>>

>> func provideWidgetString() -> String {

>> return widgetString // now legal, references

>> SimpleProvider.widgetString

>> }

>> }

>> doThingsWithWidget(provider: WidgetStringProvider(widgetString:

>> widgetString))

>> }

>> ```

>>

>> That's boilerplate I don't want to write though, as it somewhat

>> detracts from the readability of the structure. I'm not super

>> interested in defending the concept of local class definitions itself,

>> it's already allowed in the language, I'm just interested in the

>> syntax limitation and where that line might be able to be redrawn.

>> _______________________________________________

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

_______________________________________________

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

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

The point here is that the escape of the inner value necessitates the (implicit) escape of the outer captured value. That's something closures don't have to deal with.

~Robert Widmann

2016/12/24 13:13、Callionica (Swift) <swift-callionica@callionica.com> のメッセージ:

···

I may be missing your point about lifetime extension problems. How would lifetime extension be different for captures by classes than for captures by function closures?

On Sat, Dec 24, 2016 at 12:00 PM Robert Widmann <devteam.codafi@gmail.com> wrote:
That still doesn't answer the lifetime extension problems. What is being described here is a complex extension to the lifetime of memory that is only being kept alive because of these implicit captures. It sounds like what you want is a member, and I'm not sure it's worth saving 5 lines to avoid that fact.

~Robert Widmann

2016/12/24 11:45、Callionica (Swift) via swift-evolution <swift-evolution@swift.org> のメッセージ:

Missed swift-evol in my reply

---------- Forwarded message ---------
From: Callionica (Swift) <swift-callionica@callionica.com>
Date: Sat, Dec 24, 2016 at 9:44 AM
Subject: Re: [swift-evolution] Should we relax restriction on closing over outer scope in class declarations?
To: Robert Widmann <devteam.codafi@gmail.com>

These are good questions.

I believe Swift closures currently do implicit self-capture. I wouldn't expect that behavior to be different when capturing for classes.

If a class needs to refer to an outer self, I would expect the user to have to give the outer self a new name by assigning to a local. Same deal for referring to members in an outer class where there are members of the same name in the inner class: assign outer self to a local and refer to members through that local.

On Fri, Dec 23, 2016 at 10:16 AM, Robert Widmann <devteam.codafi@gmail.com> wrote:

On Dec 23, 2016, at 1:10 AM, Callionica (Swift) via swift-evolution <swift-evolution@swift.org> wrote:

I'm certainly assuming that users have a basic understanding of scope (without that, what would be the intention behind defining the class within the function?).

Despite its simplicity to you (given the example you’ve cited) this feature is quite an advanced extension to the current scoping rules.

I'm not clear on what you see as the downside of letting classes capture locals for users that are unaware of capturing.

I’ll be blunt: This opens a gaping hole in the semantics of variable lifetimes and potentially introduces an ambiguity into resolution of the `self` keyword. For the former, consider a light extension to your original example that introduces a nested class:

class Outer {
  let widgetString: String = createWidgetString()

  // Initializer suppressed
  func foo() -> WidgetStringProvider {
    class SimpleProvider: WidgetStringProvider {
      // Initializer suppressed
      func provideWidgetString() -> String {
        return widgetString
      }
    }
    return SimpleProvider()
  }
}

Now, from SIL’s point of view, widgetString must escape the scope it’s in and its lifetime should be extended beyond this block. Fine. But what about Outer? Because we’re returning a reference to SimpleProvider which escapes the scope of foo(), and instance members of a SimpleProvider capture outer context, this means we also have to keep Outer alive and around for destruction (perhaps at the point where SimpleProvider is destroyed?)

For the latter consider trying to explicitly reference widgetString here. In provideWidgetString() we close over self in both foo() and provideWidgetString(). So, if I wish to make reference to Outer’s self explicitly, how would I go about doing it? Given that today a nested aggregate can have members with the same identifiers as their parent, how do we know if we’re capturing them or not when they’re referenced in the inner aggregate?

~Robert Widmann

On Thu, Dec 22, 2016 at 11:26 PM Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
Only if you're also assuming that people defining classes within functions would know about capturing. This violates the principle of progressive disclosure, since people naturally learn about functions and classes before they learn about closures and capturing.

On Fri, Dec 23, 2016 at 01:51 Callionica (Swift) <swift-callionica@callionica.com> wrote:
Assuming capture were allowed, people defining classes within functions who didn't want them to capture could position the class definition prior to any other code in the function so that there would be nothing to capture.

On Thu, Dec 22, 2016 at 4:13 PM Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:
I have to agree with Michael; it seems dangerous to allow implicit capture by classes. A primary purpose (telos?) of closures is to provide this functionality, which is actually quite an advanced concept. One knows to think carefully about this when encountering a closure expression. A primary purpose of classes is to provide for encapsulation of code. Accidentally extending the lifetime of a local variable in a containing scope would be hard to notice and highly unexpected functionality. Better not to mix these things.

On Thu, Dec 22, 2016 at 17:54 Micah Hainline via swift-evolution <swift-evolution@swift.org> wrote:
That's exactly what I'm suggesting, the class declaration could work similarly to a closure.

> On Dec 22, 2016, at 4:15 PM, Michael Ilseman <milseman@apple.com> wrote:

>

> Are you asking for a class declaration to implicitly capture and extend the lifetime of local variables? That seems like something that’s better done explicitly. Perhaps it’s better to think about how to reduce the boiler plate code, e.g. better default initializers.

>

> (this is of course, additive and beyond the current scope of Swift 4 phase 1 planning)

>

>> On Dec 22, 2016, at 2:39 PM, Micah Hainline via swift-evolution <swift-evolution@swift.org> wrote:

>>

>> Currently we allow declarations of a new class in local scope, but

>> nothing can be referenced from the outer scope. Thus this is illegal:

>>

>> ```

>> func foo() {

>> let widgetString: String = createWidgetString()

>> class SimpleProvider: WidgetStringProvider {

>> func provideWidgetString() -> String {

>> return widgetString // Illegal, defined in outer scope

>> }

>> }

>> doThingsWithWidget(provider: WidgetStringProvider())

>> }

>> ```

>>

>> I'm trying to feel out the edges of this decision, and figure out why

>> exactly this isn't allowed now, and how we might want to relax this in

>> the future if possible. While not a common construct, it is a useful

>> one.

>>

>> Obviously there are ways around it, very simple to create an init and

>> a private let and do something like this:

>>

>> ```

>> func foo() {

>> let widgetString: String = createWidgetString()

>> class SimpleProvider: WidgetStringProvider {

>> private let widgetString: String

>>

>> init(widgetString: String) {

>> self.widgetString = widgetString

>> }

>>

>> func provideWidgetString() -> String {

>> return widgetString // now legal, references

>> SimpleProvider.widgetString

>> }

>> }

>> doThingsWithWidget(provider: WidgetStringProvider(widgetString:

>> widgetString))

>> }

>> ```

>>

>> That's boilerplate I don't want to write though, as it somewhat

>> detracts from the readability of the structure. I'm not super

>> interested in defending the concept of local class definitions itself,

>> it's already allowed in the language, I'm just interested in the

>> syntax limitation and where that line might be able to be redrawn.

>> _______________________________________________

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

_______________________________________________

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

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