Anonymous Structs

To be able to compare different closures, you'll need to wrap each into a container similar to AnyHashable, or in the future any Hashable.

But it would be useless anyway, because different anonymous closures wouldn't be equal even if they have the same data.

Thats the intention. If you think of them as closures - they may capture the same data, but may execute different code on that data.

Structurally analysing closure bodies is too much, IMO. This penaltizes only copy-pasted code. If one needs the similar closures to compare as equal - it is always possible to extract a function.

I think the optimization gets much harder once you store the closure and let it sit around for a while, since you have to know where the closure was made to do optimizations on it. Extra hard once you store it in a struct. I think the difference is similar to the difference between View and some View, which we just added to address the same problem in existentials (even if you can do the optimization when you send it through a function, we have trouble doing it when we store it in a struct)

Why?

Rhetorically speaking, the Motivation section of this proposal didn’t sufficiently or articulately answer this question, at least to me.

4 Likes

Sure, I get the difference between an existential and a generic type. My question is why this needs new language syntax -- why does the compiler suddenly have this information necessary for unboxing if I write a {{ ... }} rather than { ... }?

That's really the thing I still don't entirely understand. From what @Joe_Groff said, my understanding is that he would essentially like to transform closure-taking functions in to generic ones (like the ExistentialSpecializer does), and give some closures (but not all of them?) individual representation in the type system, so the functions can be specialised.

And my issues are:

  • Why not just make all closures "anonymous structs" internally? Why the new syntax?
  • (new) How is this different from inlining?

So, it's my understanding that a function body is required for specialisation (i.e. same module or @inlineable). Even with this proposal, you would only actually see unboxing if the closure-taking call's body was available and able to be specialised. So why not just make the inliner more aggressively inline closures?

1 Like

As I read it, no new syntax is being proposed, only the generalization of closure syntax into matching generic contexts. Indeed, closures are effectively anonymous types internally, and this could be a way of exposing the context type to the programming model.

Specialization and unboxing are separable concerns. Variables of generic type are still unboxed even if they aren't specialized. A closure context formed for a protocol conformance, similarly, would be storable in-line even if its body is not available for inlining, since its type information would be able to propagate.

4 Likes

I think we should consider this syntax alternative (or some other alternative which still uses the struct keyword):

let eq = struct _: Equatable { [x, var y = x + 1] }

It makes it clear that this is a struct, not a closure.

4 Likes

I agree that this proposal would be better if every form of anonymous struct started with the keyword struct, regardless of how abbreviated the rest of the syntax might be.

OTOH, I'm not a fan of requiring the : there or the _. Instead, how about something like:

let eq = struct  { [x, var y = x + 1] } : Equatable

with the understanding, of course, that the conformance could be omitted if it can be inferred.

6 Likes

I can definitely see the value here. Being able to provide inline types conforming to protocols would be great. My initial feeling is that the syntax is too close to closures. I'd prefer either using existing struct or tuple syntax to make it more clear. For example:

// `struct` syntax
let equatable: some Equatable = {
  let x = 0;
  let y = 4;
}
// `tuple` syntax
let equatable: some Equatable = (x: 0, y: 4)

Just using existing struct syntax out of the box would allow more flexibility:

let stringConvertible: CustomStringConvertible = {
  var description: String { "Hello, world" }
}

I'm not sure how I feel about using trailing closure syntax and automatically conforming to protocols with a single required method with closure syntax. It could make code difficult to reason about and potentially ambiguous.

1 Like

Anonymous Structs stop being anonymous if you use any of those words.

They are anonymous if they are unnamed. Keywords don't name them.

4 Likes

This part kind of threw me since Swift closures capture by reference by default (unlike Objective C blocks). I just spent too much time chasing a bug because of the difference between ObjC and Swift in this regard. That made me think that having it be different between closures and anonymous structs in Swift would be potentially confusing.

1 Like

There were some interesting suggestions in this thread. Well an 'unnamed' struct might look like this.

struct {
  let x = 42
}

So why not use the struct or class keywords before the curly brackets?

let x = 0
let eq: any Equatable = struct { 
  [x] // capturing list
  let y = 42 // normal properties that can't capture from outer context 
}

It's simple, signals its intent right away and allows for anonymous struct private storage.

18 Likes

This proposal covers two things:

  • closures conforming to protocols
  • anonymous structs

Which are combined into single proposal because closures conforming to protocols are implemented as anonymous structs.

But from this thread I see that trying to combine two into single syntax is causing a lot of confusion.

So, let's try to take a step back and separate them.


Part 1. Closures conforming to protocols

That's a much simpler case, and I was able to work out a pretty complete proposal document - swift-evolution/NNNN-closures-as-structs.md at master · nickolas-pohilets/swift-evolution · GitHub.

Part 2. Anonymous structs and classes

I'm trying to rework this to look more similar to Java's anonymous classes. Roughly like this:

protocol Foo {
    var x: Int { get }
    var y: Int { get set }
    func doIt()
}
let x = 0
let eq = struct Equatable & Foo {
    var x = x
    var y = x + 1

    func doIt() {
        print("doing...")
    }
}

If protocol requirements can be inferred from the context, they can be omitted. Trailing syntax is supported similar to closures.

func use<T: Equatable & Foo>(_ x: T) { .. }
f() struct {
    var x = x
    var y = x + 1

    func doIt() {
        print("doing...")
    }
}

Classes are supported too. I think anonymous classes might be an answer to lack of private protocol conformance in Swift:

// Need to retain somewhere, because `scrollView.delegate` is weak.
weak var zelf = self
self.scrollViewDelegate = class UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        zelf?.updateUI(offset: scrollView.contentOffset)
    }
}
scrollView.delegate = self.scrollViewDelegate

There are still 2 major open questions:

  • syntax for calling super.init for classes
  • disambiguating self and Self for both structs and classes.

For the last one simplest strategy would be to let self and Self to always refer to the anonymous struct/class and use workaround like in example above to use self from parent context.

Regardless of the disambiguation strategy properties of self from parent context are available by their name with self. But if they are used inside a closure, compiler require explicit self.

6 Likes

I think looking to anonymous inner classes in Java for inspiration is an excellent idea. That was what came to mind immediately when I read the proposal.

FWIW, Java uses “qualified this” to reference the outer object, spelled OuterTypeName.this. That’s wordy for Swift, but maybe better than weak var zelf = self? :slight_smile:

2 Likes

I thought about that form and decided against it because it will confuse the compiler for sure, or it least make it difficult to parse user code correctly.

let eq = struct Equatable { ... }

Is this an anonymous struct Equatable or an attempt to shadow the Equatable protocol? There are let eq = tokens, but is this really what we want the compiler to look for to disambiguate the two different scenarios?

Therefore I prefer to use any or some keywords.

let eq: some Equatable = struct { ... }
4 Likes

In Java it would be new Equatable() { ... };. Swift has no new keyaord but still uses () for constructor calls. So directly translateted it would be let eq = Equatable() { ... }

It does not work with composition (and Java don't support it).

   let eq = struct Equatable & MyProtocol { }
1 Like

Just thinking out loud.

Option 0.

self inside anonymous stuct/class always refers to the instance of the anonymous struct/class. If self from the parent context needs to be captured - you need to store in a local variable.

Option 1.

Anonymous structs/classes don't have their own self. self always refers to the instance of the named struct/class.

Option 2.

OuterTypeName.self is already reserved in Swift - this returns a type object. This cannot be used.

Option 3.

self as OuterTypeName. This gives a false promise that self of anonymous struct/class may be casted to self of the parent scope dynamically.

Option 4.

Captured variables a turned into stored properties, probably with the same name. So, if self from parent scope would be captured, this would create a property with name `self`, which would be escaped by backticks to distinguish from the keyword. For a moment I though that backticks could be used to disambiguate between keyword self referring to instance of anonymous struct/class and `self` referring to the self captured from the parent scope. But it is not that simple.

First of all, to capture self from the outer scope you need to refer to the self inside the anonymous struct/class. But when referring to `self` you are not really referring for self.

Second - what if there is already `self` in the parent scope? Nested anonymous structs/classes would produce such situation. If backticks would work somehow, probably it would be something like `self`.`self` to refer to self from grand-parent scope. And this leads us to

Option 5.

Let self refer to the innermost struct/class, and self.self to the self from the parent scope, self.self.self to the self from grand-parent scope, etc.


Summary

Out of this zoo, I think realistic ones are 0, 1, and 5