Circling back to `with`

That's what I'm afraid of too. I don't like the self rebinding in a sense that only self can be rebind. Why not explicitly tell which parameter should be rebind?

class SomeView : UIView {
  func foo() {
    // explicitly bind `$0` since passing a tuple to `with` can add more `$#` parameters
    UIView() with { bind $0 in 
       tag = 42               // executed on `$0`
       backgroundColor = .red // executed on `$0`
       self.addSubview($0) // self is the outer SomeView
    } 
  }
}

I like this solution a lot more than just polluting the namespace with the argument just becoming an implicit self for the scope of the closure.

I'd assume a self-rebinding feature would mostly be used to create DSL-ish APIs, and for those I think it would be better to have the rebinding in the closure type declaration, to have less syntax (in DSLs potentially repeated many times) at use point.

In Kotlin, you can declare what they call function type with receiver like this:

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()
    html.init()
    return html
}

which I find rather nice, although as mentioned above, Kotlin has also solved the problem of accessing shadowed things from outer scopes.

If we had this, projects could build some very cool DSLs, e.g. adding an initializer to UIView subclasses which took a closure like this, so you could do something like:

let label = UILabel {
    text = "hello"
    textColor = .red
    font = //...
}
2 Likes

The latter is neat and such, however I once talked with @hartbit about such a functionality (without rebinding) and he quickly convinced me that this addition would be ambiguous if some other already existing init had a trailing closure.


The following idea is purely bikeshedding so please don't interpret too much in it.

I would like to see a semi-automatic rebinding which would be part of the function parameter type like inout is. In that sense the function from way above would bind the inout T (not self!!) and provide a very nice usability:

@discardableResult
public func <- <T>(lhs: T, rhs: (bind inout T) throws -> Void) rethrows -> T
let label = UILabel() <- {
  text = "swift"
  textColor = .white
}

So bind is completely optional in that sense and can only be used on function parameters. There could only be at most one parameter that can be bind. The AST printer should only print bind on closure parameters, other funtions and methods can still use this feature but exposing it from outside the module does not make sense.

// imagine this is a method of a large type.
// this should be valid
func foo(param1: bind SomeClass, param2: SomeOtherType) {
  // `param2` must stay explicit
  someMethod() // implicit `param1`
  Module.someMethod() // Resolving ambiguity
  self.doSomething(with: param1)
}
1 Like

While I see what you're going for, my initial reaction is that it's a bit too powerful for what's meant to be a convenience feature. The idea of using anything more than self for implicit lookup seems potentially confusing to me, and if you really need to access self, you can just do so outside of the closure.

The ‘anonymous methods’ idea seems a much more direct solution to this problem with is meant to solve; it's lightweight and much easier to reason about.

Other the other hand, the benefit of your solution is that it's perfect for DSLs, and as your example shows, it can be used to create a with function/operator. If this is a problem we want to solve, it does make more sense to go in this direction, and turn with into a library feature.

I think that if rebinding the implicit method lookup target is desired, self should be able to be used as an identifier as discussed here, with the exception that the implicit method lookup is the current overload for the self identifier.
eg. in

struct Example {
   func method() {}
}

struct Main {
  func method() {}
  func test() {
    method() // calls Main.method (via implicit self)
    do {
      let self = Example()
      method() // calls Example.method (via explicit self)
    }
  }
}

[editing on the iPad is terrible :-/]

I think that doesn't fit:

let number = "42"
let number = Int(number)

Why should this be forbidden, when it's allowed for self?

Your example doesn't match the way it is discussed in the other thread. They want to be able to make [weak self] strong when entering a closure. Being able to just override self by itself seems like a very bad idea; nowhere else do we allow type changes like this. With guard you can do some type changes but it doesn't force variable occlusion like this does with old self.

I am fine with rebinding self but it should only be at the start of a new scope and only when explicitly requested.

Sorry I should have linked the explicit post where it was mentioned, but didn't want to look though the thread, so I should have linked this quote directly:

Although, I see my example was indeed incorrect for my interpretation, which would indeed require the new overload for the identifier self to be in a new scope. (will edit it)
Additionally, my idea would be explicitly requested, by naming the new variable self.

1 Like

I think the way you have updated it is fine but would prefer if the syntax were more like this (tweaked from the code above):

struct Main {
  func method() {}
  func test() {
    method() // calls Main.method (via implicit self)
    do { [bind Example()] // Yes, I know this is calling a constructor and I intended it that way.
      method() // calls Example.method (via explicit self)
    }
  }
}

Ideally we could use the [bind Foo] syntax (or something similar) at the creation of any scope so you don't have to make and call a closure to do it.

My thoughts are that rebinding self should only be done via special syntax unless your are binding a weak self to self.

From my quick skimming of this thread, I don't think anyone has brought up this possible utility of a first-class with keyword yet.

An with keyword described by Xiaodi earlier, should additionally work with enum/struct/class declarations. Example:

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

var mutable: Int = 0

// `with` should work in this way as well…
with M {
  mutable = double(a)
}

// `mutable` is now 42

In other words, the scope following the with keyword is a place where Self becomes implicit the way self is implicit in methods.

In practice, putting static properties under an case-less enum is the closest thing we have to a namespace in Swift. This addition will bring this use case closer to the ergonomic of an ML-style module syntax without introducing a whole new set of language features.

I mused about this a few months ago, and for this desire of mine, I'm against any introduction of with as anything short of a first-class keyword.

(side note: another thing I wish would one day exist is a shorthand to make everything in an declaration implicitly static, aka

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

…would be a sugar for M from the previous example.)

3 Likes

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?

1 Like

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?

2 Likes