Circling back to `with`

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

Yes, it might look odd, but I'd expect "<-" to be an assignment or to feed something to the item on the left, and "with" could mean anything.
On the other hand, interpreting "2 * (5, 10)" as "(2 * 5, 2 * 10)" feels very natural for me.

Coming from C

if (condition)
    f();
if (condition)
   g();

can be replaced with

if (condition) {
  f();
  g();
}

(assuming condition is const ;-)

IMHO, its only a small amount of pollution for a lot of extra generality.

Yes, I'm not sure at all. I should look at Erika's examples and look at how they'd look in both options.

I lean towards feeling that the gain in clarity that comes with explicit rebinding is worth it. On the other hand I am sympathetic to the position that explicit rebinding could become boilerplate-y in some settings (specifically builder style DSLs). Imagine the following HTML builder (inspired by https://kotlinlang.org/docs/reference/type-safe-builders.html):

html { self in
    head { self in
    }
    body { self in
    }
}

If supporting these use cases is important perhaps there could be a shorthand for single-argument closures that bind the argument to self. Something like {@ ... } which reduces the 7 characters of self in to a single @. This would provide most of the syntactic benefit of implicit rebinding without losing clarity. (Note: this is strawman syntax, to demonstrate the concept, not a well thought out proposal)

The above example might be streamlined to the following:

html {@
    head {@
    }
    body {@
    }
}

This is pretty close to the original Kotlin without the potential for confusion. Readers who are unfamiliar with the DSL but familiar with Swift would immediately have a pretty good idea of what is going on.

2 Likes