Circling back to `with`

If we had a bind annotation in closure parameter type lists, as @DevAndArtist suggested, we would come very close to this, and additionally could use it in a number of other ways.

Swift has precedent for making it possible to create language-style features in libraries (see @autoclosure, trailing closure syntax and custom operator support), which is, in my opinion, a good way to approach things.

Coming back to your example, this is possible today:

func using<T>(_ val: T.Type, do closure: (T.Type) -> Void) {
    closure(val)
}

enum M {
    static let a: Int = 21
    static func double(_ n: Int) -> Int { return n * 2 }
}

var mutable: Int = 0

using(M.self) {
    mutable = $0.double($0.a)
}

Now if using could be declared as func using<T>(_ val: T.Type, do closure: (bind T.Type) -> Void), you could drop the $0, and we’re quite close to your example except for the .self (which is something we probably want to get rid of at some point).

1 Like

I’m sorry, this may seem close to you, but using $0 is explicitly not what Xiaodi or I wanted. In fact, in my working environment, complex or multiple expressions with implicit closure arguments is banned in the style guide due to poor readability.

I think there’s merits to what you said and it’s great that we can simulate this style today without much change to the language. But I want to be clear that we are talking about very different things.

Please read my complete post:

2 Likes

Exactly, this way it eliminates the burden with only allowing to bind self, and still allows you to use the parameter explicitly. bind on function parameter would be a complete convenience opt-in thing.

Ah, my apologies. You mentioned “this is possible today” followed by the code example and I made a conclusion. I should’ve read your entire post.

I still think we are talking about 2 agnostic features even tho they could have similar effects. At the moment I have no opinion about the bind proposal (also, should we discuss that in a separate thread?)

1 Like

Feel free to open a new thread for this if there is no yet. The current thread feels exactly like the thread about Result becuase there is another feature that could be introduced first and would have an impact on the design of the current pitch.

1 Like

Okay. How? Any idea where a starting point for this? Or for something more along the lines of a discardable-result function that operates on a value, allowing that to be treated as the instance for self and allowing you to omit the self reference in use?

binding(someValue) {
   baz.append(self) // `someValue`
   foo = someMethod() // `self.someMethod()` aka `someValue.someMethod()`
}

I’ve been browsing Swift source and I don’t even see a place where I could start poking. What’s the opposite of a silo?

Internally, self is already treated more or less like any other variable binding, which is part of why the guard self = self trick used to work by accident. We recently “fixed” this by making it so that self is a “special identifier” rather than a normal identifier. If we were going to support rebinding self officially, then we could parse the self token into this special identifier in contexts where we want to allow self-rebinding (if/guard conditions, closure arguments, maybe everywhere?). Unqualified name lookup might also have to change to do lookup based on the current self binding rather than assuming the self binding is a particular method argument as well.

3 Likes

So the “fixed” version doens’t allow me to do this anymore? Right?

let dateFormatter = with(DateFormatter()) {
    let `self` = $0
    self.dateStyle = .short
    self.timeStyle = .long
}

Ideally, what I want to do is this:

let dateFormatter = with(DateFormatter()) {
    dateStyle = .short
    timeStyle = .long
}

How hard is it to get from here to there? Where could I start looking in the compiler? Also, I’m aware that there would be an issue if someone got cute and did this:

let dateFormatter = with(DateFormatter()) {
    dateStyle = .short
    timeStyle = .long
    otherField = with(SomeType()) {
         // what is self here? Can I refer to "timeStyle" and have it know
         // that's scoped to the dateFormatter and "foo" scoped to the
         // `SomeType` instance, the way I'd expect in nested functions?
    }
}

Thoughts?

1 Like

Wrt nested blocks with self-rebinding (which probably applies equally to self-rebinding in an instance method):

We can rephrase that question like this: Does what we’re talking about as “self-rebinding” (a) literally bind a new value to self and then you can use the existing ability to omit self., or does it (b) take the value and bind all it’s members into the current scope?

I’m currently thinking that (a) is simpler in terms of mental model, and is probably a less intrusive change implementation-wise as well (although I don’t know much about the compilers inner workings).

Fwiw, Ruby works like (a), while Kotlin works like (b).

Plus, we can always go from (a) to (b) without breaking compatibility, not in the other direction.

FYI regarding self-rebinding: https://github.com/apple/swift/pull/15306

1 Like