Circling back to `with`

I’m afraid I don’t understand. Neither of those examples require with to be an expression instead of a statement.

Let me put it in different words.

  • Does the first class with return a value like the <- operator function or not?

  • Does it support optional chaining so that between {} the value is non-optional?

+1 on the general idea. I’ve used with in other languages and really liked it.

I do like the convention of automatically returning self or an identically typed entity. And the proposed implementation of that here is quite clever and transparent.

Overall, though, I’m not so sure about this specific proposal. Encouraging developers to replace variable names with $0 doesn’t seem like an improvement, and the proposal punts on the most valuable part of with, namely, self rebinding. That’s the thing that would really clean up code and add expressive possibilities.

It’s kind of implicit in this proposal that doing the right thing would be too hard or too controversial, so we should just content ourselves with putting in some kind of half-baked patch to ease a few common pain points. I’m all for clever hacks, but why not just let people copy the implementation into their own projects? It seems like too much of a stopgap solution to be worthy of being blessed as official Swift stdlib API.

Maybe it really is too hard to implement self rebinding anywhere in the foreseeable future. It appears (to my unsophisticated eye) analogous to defining and then calling a type-specific extension, but currently that is not possible within the context of a function. I don’t know the implementation history behind that restriction; perhaps there’s a very good reason why it can’t be done. If so, oh well; Swift doesn’t have with.

This would be really nice. I’d suggest that with should be infix so it reads more naturally.

let translated = point with {
    x += 10
}

This works as of the current swift:

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

I’m not super happy about the $0 sticking around but it’s a lot cleaner than it used to be. However, I believe @Chris_Lattner3 has called this an abomination so it’s unlikely that this would be used as a motivating case. Ideally, there would be a way to scope to a variable without having to use either abomination or $0.

cc @beccadax

We could steal an idea from Kotlin and let you bind self in the scope of a closure:

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

which seems like a reasonable extension of the much-demanded guard let self = self rebinding idea.

11 Likes

Doesn’t this follow from the shadowing option (#1) in that thread? In that thread I shared another example where this kind of self shadowing by a closure argument would be useful.

1 Like

Yeah, sorry for not spelling it out exactly, but that was the analogy I was going for.

Even better, go full Kotlin and bind self automatically.

4 Likes

I would love that.

I don’t mind going full Kotlin on that either.

1 Like

The automatic-ness of the Kotlin design makes me wrinkle my nose a bit. Having a little bit of notation in the closure literal to show that self rebinding is happening seems like a small price for writers to pay for the benefit of anyone reading the code. It also puts control of the feature completely in the author’s hand; they don’t only get to use it where the API author allowed them to. If we do end up allowing you to rebind self in guards and if-lets, then it also feels like one coherent feature, as @anandabits said.

2 Likes

Just tested and this works

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

and this does not (which isn’t exactly unexpected)

let dateFormatter = with(DateFormatter()) {
    `self` in
    dateStyle = .short
    timeStyle = .long
}
3 Likes

I can see a manual rebinding case being useful, but while you may be right that the capture is useful to call out the rebinding, I think the use of with will do that too. Automatic binding allows users to drop the boilerplate from the common case, as we see in several Swift features.

2 Likes

Really nice. Please bring it to Swift 5.

Maybe straying a bit off-topic, but here goes:

If we want to enable binding self automatically, would that only work for with, or could we make a generally useful feature out of this?

Ruby has instance_exec, which executes a block in the context of some object. This method is what makes Ruby so great for building DSLs, and I could see a feature where a function can declare it will execute a closure with self bound to an instance of some type as a nice general-purpose addition to Swift. Maybe the syntax could be as simple as allowing something like an @self annotation once in the parameter type list of a closure type.

Yes. I’d rather have self-rebinding being activated on the method definition rather than at the call site. Self-rebinding at the call site is seducing because it is more general, but it would pollute all calls to with or similar convenience method whose purpose it to streamline boring code.

2 Likes

I kind of agree with this. I’ve seen some people struggle with Kotlin precisely because of all the magic that does and by having different keywords that do too similar things.

Also is important to think about how accessing the enclosing type would look like in terms of shadowing self. (i.e: how I access a property in the class from code inside the “with” if self is being shadowed)

Not a crazy idea. Going even further some languages have a “using Type/Instance” keyword that rebinds the names in that object to the current scope. Probably Swift doesn’t want that but still, something powerful that we could keep in mind.

1 Like

If any special syntax is added for such functionality, I’d favor

var v: [Int]?
//....
v?.{
    // do something with v - if it's not nil
}

Imho it’s more intuitive than all alternatives (and more concise).

4 Likes

I does look strange to me. I’d prefer one of these

  • v?.with { ... }
  • v? with { ... } (infix first class with)
  • v? <- { ... }
  • with v? { ... } (here it’s not obvious that it can return a value though)

This is very interesting. Basically we already have unnamed functions, and this would add unnamed methods.

1 Like