Proposal: Closures capture weak by default


(Joe Groff) #1

I’m really not found at all of having `let` properties captured by closures when used without explicit `self`.

Three reasons:

REASON 1. When `self` is not mandatory, then adding self should have no impact whatsoever.

Why should one add a self that is not mandatory?

1. for symmetry (1):

  self.foo = other.foo
  let delta = self.x - other.x

2. for symmetry (2):
  
  init(a:String, index:Int) {
    self.name = name
    self.position = index+1 // keep 1-based value for implementation reasons
  }

3. add your own reasons to use an explicit self even when not required, and I’m sure you did.

So generally speaking, using self when not mandatory should have no impact on the program at all.

REASON 2. What happens when a property starts its life as `let`, but turns `var` eventually? For example, the property eventually becomes lazy. OK now it’s is illegal to use the property without explicit `self` in closures, and you break a bunch of code. And you need to bump the major version of your package. Just because you turn a `let foo: String` into `lazy var foo: String = …`.

That’s not good at all.
REASON 3. It’s simply not clear. Clever, terse, smart, brilliant, but not clear.

Compare to:

  // Obviously only `foo` is captured, and not self:
  let foo = self.foo
  performClosure { foo }

These are good points, but re: reason 2, turning a public 'let' into a 'var' is already a non-resilient, semantics-breaking change, since you're taking back a promise you made about immutability. You should publish a public 'var' with a private setter if you want to reserve the right to publish mutability in the future.

As for reason 3, the only noticeable difference between capturing an immutable property and capturing its container is that the container's lifetime might be shorter than it otherwise needs to be, so you save some memory, and weak references maybe null out sooner. If you were going to capture a weak reference to break a cycle anyway, the latter isn't a problem, and capturing the immutable value directly is preferable, since instead of disappearing, it will still be independently held by the closure.

-Joe

···

On Dec 8, 2015, at 9:28 AM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

My two cents.
Gwendal Roué

Le 8 déc. 2015 à 18:01, Paul Cantrell via swift-evolution <swift-evolution@swift.org> a écrit :

For 'let' properties of classes, it'd be reasonable to propose having closures capture the *property* directly by default in this way instead of capturing ‘self'

That would be fantastic.

I’ve also wondered whether there’s a tidy way for a closure to be tied to the lifecycle of self, so that when self goes away, the closure goes away too. That’s often the desired behavior. On a casual thinking through, though, it seems like a can of worms — and the “guard let self = self else { return }” proposal on another thread gives most of the same benefit.

P

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


#2

I’m really not found at all of having `let` properties captured by closures when used without explicit `self`.

Three reasons […]

REASON 2. What happens when a property starts its life as `let`, but turns `var` eventually? For example, the property eventually becomes lazy. OK now it’s is illegal to use the property without explicit `self` in closures, and you break a bunch of code. And you need to bump the major version of your package. Just because you turn a `let foo: String` into `lazy var foo: String = …`.

That’s not good at all.

These are good points, but re: reason 2, turning a public 'let' into a 'var' is already a non-resilient, semantics-breaking change, since you're taking back a promise you made about immutability. You should publish a public 'var' with a private setter if you want to reserve the right to publish mutability in the future.

Is it so sure? I don’t see any difference, API-wise, between `let foo` and `lazy var foo`. Maybe I forgot the private(set), but in this case please be nice and follow me: I’m talking about a public getter that starts its life as `let` and ends its life as `lazy var`. There is no API difference in this case, and no reason to bump the major version of a package that would expose such a getter.

Besides, a non-lazy property that starts its life as `let foo` and ends as `private(set) var foo` is in the same category: there is *no* difference in the public API, and *no* reason for bumping the major version of the lib.

And this is why I keep standing against your proposal of having the presence or absence of `self` change the capture mode of such properties in closures, despite the fact it looks smart at 1st sight.

REASON 3. It’s simply not clear. Clever, terse, smart, brilliant, but not clear.

Compare to:

  // Obviously only `foo` is captured, and not self:
  let foo = self.foo
  performClosure { foo }

As for reason 3, the only noticeable difference between capturing an immutable property and capturing its container is that the container's lifetime might be shorter than it otherwise needs to be, so you save some memory, and weak references maybe null out sooner. If you were going to capture a weak reference to break a cycle anyway, the latter isn't a problem, and capturing the immutable value directly is preferable, since instead of disappearing, it will still be independently held by the closure.

This does not address my point. My point is that the current state of the language makes it crystal clear about the captured objects, when your proposal makes it 1. unclear, and 2. breaks the fact that the non-mandatory `self` should remain *discretionary*.

I’ve given symmetry examples above. Here is one another:

  let foo = self.foo // discretionary foo
  self.foo = nil
  // Now self.foo is nil and can expose its new clean state to other objects, for example an eventual delegate.
  // Yet foo is still usable, for whatever purpose.

Without the discretionary self, we’d have to use ugly variable names like `theFoo`, `aFoo`, `myFoo`, or whatever funny names that are nor required today:

  let theFoo = foo
  foo = nil

To sum up: non-mandatory `self.` should remain discretionary. Hence any idea that is based on the absence or presence of an explicit `self.` is a bad idea.

Gwendal Roué


(Joe Groff) #3

There *is* an important difference in API between "nobody can mutate this" and "you can't mutate this, but others may be able to." The former is a much stronger guarantee that can't be safely weakened without breaking code.

-Joe

···

On Dec 8, 2015, at 10:48 AM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

I’m really not found at all of having `let` properties captured by closures when used without explicit `self`.

Three reasons […]

REASON 2. What happens when a property starts its life as `let`, but turns `var` eventually? For example, the property eventually becomes lazy. OK now it’s is illegal to use the property without explicit `self` in closures, and you break a bunch of code. And you need to bump the major version of your package. Just because you turn a `let foo: String` into `lazy var foo: String = …`.

That’s not good at all.

These are good points, but re: reason 2, turning a public 'let' into a 'var' is already a non-resilient, semantics-breaking change, since you're taking back a promise you made about immutability. You should publish a public 'var' with a private setter if you want to reserve the right to publish mutability in the future.

Is it so sure? I don’t see any difference, API-wise, between `let foo` and `lazy var foo`. Maybe I forgot the private(set), but in this case please be nice and follow me: I’m talking about a public getter that starts its life as `let` and ends its life as `lazy var`. There is no API difference in this case, and no reason to bump the major version of a package that would expose such a getter.

Besides, a non-lazy property that starts its life as `let foo` and ends as `private(set) var foo` is in the same category: there is *no* difference in the public API, and *no* reason for bumping the major version of the lib.


#4

This is true, you are right. The property could be mutated by the package internals.

This paves the way for another swift evolution request, actually. The `var` in lazy and the `private(set)` are often plasters on properties that are *intended* to be immutable, but can not be because of the current limitations of the language.

Gwendal

···

Le 8 déc. 2015 à 19:51, Joe Groff <jgroff@apple.com> a écrit :

On Dec 8, 2015, at 10:48 AM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

I’m really not found at all of having `let` properties captured by closures when used without explicit `self`.

Three reasons […]

REASON 2. What happens when a property starts its life as `let`, but turns `var` eventually? For example, the property eventually becomes lazy. OK now it’s is illegal to use the property without explicit `self` in closures, and you break a bunch of code. And you need to bump the major version of your package. Just because you turn a `let foo: String` into `lazy var foo: String = …`.

That’s not good at all.

These are good points, but re: reason 2, turning a public 'let' into a 'var' is already a non-resilient, semantics-breaking change, since you're taking back a promise you made about immutability. You should publish a public 'var' with a private setter if you want to reserve the right to publish mutability in the future.

Is it so sure? I don’t see any difference, API-wise, between `let foo` and `lazy var foo`. Maybe I forgot the private(set), but in this case please be nice and follow me: I’m talking about a public getter that starts its life as `let` and ends its life as `lazy var`. There is no API difference in this case, and no reason to bump the major version of a package that would expose such a getter.

Besides, a non-lazy property that starts its life as `let foo` and ends as `private(set) var foo` is in the same category: there is *no* difference in the public API, and *no* reason for bumping the major version of the lib.

There *is* an important difference in API between "nobody can mutate this" and "you can't mutate this, but others may be able to." The former is a much stronger guarantee that can't be safely weakened without breaking code.


(Matthew Johnson) #5

This paves the way for another swift evolution request, actually. The `var` in lazy and the `private(set)` are often plasters on properties that are *intended* to be immutable, but can not be because of the current limitations of the language.

And even worse is the need to declare things as Optional var members (usually IUO) when they can't be fully initialized in phase 1 due to dependency issues.

I would love to see proposals aimed at solving these problems.