[swift-evolution] Should we relax restriction on closing over outer scope in class declarations?

Xiaodi Wu xiaodi.wu at gmail.com
Fri Dec 23 11:45:47 CST 2016


On Fri, Dec 23, 2016 at 03:10 Callionica (Swift) <
swift-callionica at callionica.com> 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?).
>

The notion of closing over variables far exceeds what one might reasonably
call "a basic understanding of scope." I doubt I'm alone on this opinion,
as _Advanced Swift_ (an excellent text written for users already familiar
with the basics) reviews closures and closure expressions from the ground
up; by contrast, it does not review functions, classes, or even (if I
recall correctly) defining classes inside functions.

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

It violates the notion of progressive disclosure, which has been an
explicit design goal for Swift. In one of the documents on GitHub, the core
team writes: "One goal of swift is to provide a very 'progressive
disclosure' model of writing code and learning how to write code.
Therefore, it is desirable that someone be able to start out with
`print("hello world\n")` as their first application." Chris Lattner
recently reiterated this idea in a presentation he gave at IBM about the
Swift language.

The idea of progressive disclosure is that it should be possible to teach
concepts a learner must understand first without exposing them to more
advanced concepts they must pay attention to but cannot understand. Let's
look at some examples:

- In Java, one cannot write "Hello World" without being exposed to `public
static void main`. However, one cannot understand what it means to be a
public or static method until _after_ one has mastered printing "Hello
World". This violates progressive disclosure.
- In Swift, one's first "Hello World" is a one-liner that introduces the
print function and nothing else. This is progressive disclosure, and as
quoted above, Swift has been explicitly designed that way.

- In Swift, `struct Point { let x, y: Int }` is a fully usable type. With
that single line, you can now make a new `Point` by writing `let point =
Point(x: 42, y: 42)`.
- To do a similar thing in some other languages, you'd have to write a
constructor or initializer. However, one cannot really understand what
initialization is without first having initialized something, but one
cannot initialize something unless there is an initializer. This violates
progressive disclosure. In Swift, an initializer is synthesized for you;
this promotes progressive disclosure.
- There have been proposals to change the default access level to private.
If `x` and `y` were private, then `Point` would be unusable without access
modifiers for its members. However, one must first understand how structs
and other types encapsulate code before one can understand how access
modifiers work, but one would not be able to create useful structs or other
types without first using access modifiers. This would violate progressive
disclosure. In Swift, the default access level is internal; this promotes
progressive disclosure.

- In Swift, _closure expressions_ don't have to capture anything (i.e.,
they don't have to be true _closures_). But they are introduced as closure
expressions rather than, say, function expressions, and they are taught in
the same breath as the concept of capturing variables. This ensures that
every person who reaches for a closure expression knows about its potential
for capturing and extending the lifetime of a variable. Closure expression
syntax and the concept of closing over its environment are thereby
inextricably linked. Thus, one can be well assured that every user who
extends the lifetime of a captured variable--unintentionally or no--has
already been introduced to the fact that such a thing is a possibility.
- If a user who has mastered only functions and classes can silently extend
the lifetime of a variable, this would be to that user an inexplicable
behavior. One must teach someone either "never" to nest classes inside
functions, or to do so only by putting classes before any other code, but
one would not be able to explain to the learner _why_ that must be the
case. This violates progressive disclosure.

Pedagogical points aside, introducing another way to extend the lifetime of
a variable with a syntax that currently doesn't do so is, IMO, creating
another avenue for footguns. I'm not convinced that this functionality
would be used intentionally more often that it would be triggered
unintentionally and not discovered.


On Thu, Dec 22, 2016 at 11:26 PM Xiaodi Wu <xiaodi.wu at 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 at 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 at 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 at 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 at 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 at 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 at swift.org
>
>
> >> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
> >
>
>
> _______________________________________________
>
>
> swift-evolution mailing list
>
>
> swift-evolution at swift.org
>
>
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
>
>
> _______________________________________________
>
> 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/20161223/7dff1f64/attachment.html>


More information about the swift-evolution mailing list