Anonymous Structs

Same here. Something like this along the same lines as the pitch would be amazing (even if it's not what the goal of this pitch had in mind):

protocol Vector3 {
  var x: Double { get }
  var y: Double { get } 
  var z: Double { get }
}

let vector: some Vector3 = (x: 1, y: 2, z: 3)

or something like this, to allow arrays of tuples to be equatable:

protocol Vector3: Equatable { /* ... */ }

let theseVectors: [Vector3] = [(x: 1, y: 2, z: 3)] // or change to [some Vector3] if the some keyword would be necessary
let thoseVectors: [Vector3] = [(x: 3, y: 2, z: 1)]

theseVectors == thoseVectors
3 Likes

In fact, can't we somehow use tuple syntax for this? perhaps an unnamed struct with only properties could use tuple syntax, but if you need to fulfill a single-requirement it can look more like a plain closure with the struct's members in a unique place:

protocol Message {
  var text: String { get }
  function send(using service: MessageService)
}

let msg: some Message = { (text: "Hello, world!") (service: MessageService) in /* ... */ }
// or
let msg: some Message = (text: "Hello, world!") { (service: MessageService) in /* ... */ }
// or treat the unique requirement syntactically similar to another member of the tuple if possible:
let msg: some Message = (text: "Hello, world!", send(using:): { (service: MessageService) in /* ... */ })
// potentially omitting the label if it can be inferred successfully:
let msg: some Message = (text: "Hello, world!", { (service: MessageService) in /* ... */ })

edit: that last two examples feels nice to me, since that would be scalable to multiple unfulfilled requirements too, if you have a need for that and don't want to make a whole named type for it.

3 Likes

This may just be me, but I am having a lot of trouble digesting this example:

What is happening here!? Is x being captured? Is x the name of a stored property in the struct being created? Wherefore art thou x? Also, I am immediately perplexed by what var = x + 1 means (the var = without the label is really throwing me off). Is that actually referring to the y property requirement of Foo and if so, does eq.y always return eq.x + 1, or is it just initial set to the variable x that is not a member of eq + 1? So many questions, yet so little code! At first glance, all of these things are unclear and honestly, a bit more than that, but let me try to take a stab at this (please correct me if I'm wrong, I'm having a decent amount of trouble interpreting this).

From what I understand from the nature of this proposal, eq is desugared into the following:

struct _Anonymous: Foo, Equatable {
    private(set) var x: Int  \\ or let?
    var y: Int
}

let x = 0
let eq = _Anonymous(x: x, y: x + 1)

Please correct me if my understanding is incorrect. If I was indeed able to untangle that example, I think you should definitely change the let x = 0 to some other variable name, otherwise you are just sorta asking for confusion.

Also, I feel like it makes it much more unclear not requiring the argument names as when you look at eq it is not really clear how it satisfies the requirements of Foo without going through the order of the property requirements themselves. Also, if Foo happened to be in a different file, knowing that var = is actually referring to the y property is not at all immediately obvious to someone reading the code. As of now, all types (classes, structs) need to write the name of their stored properties in their implementations, not having the same requirement on anonymous structures seems quite confusing to me. For example, if I suddenly stumbled into this in code I'm reading, I would be fairly perplexed:

let x = 0
var eq: some Equatable & Foo = { [x, var = x + 1] }
eq.y = 7  // Where did that property come from?

That being said, I feel like the following would be much clearer:

protocol Foo {
    var x: Int { get }
    var y: Int { get set } 
} 
let a = 0 
let eq: some Equatable & Foo = { [x = a, var y = a + 1] }

If I totally missed the mark here, please feel free to correct me. I like the idea of this feature a lot, but the syntax is really throwing me off.

10 Likes

This reminds me of all the hullaballoo around ExpressibleByTupleLiteral, which has been brought up a lot (1, 2, 3, 4). All of these sort of end up saying though that we should probably wait to get variadic generics before we can get this type of thing though.

1 Like

It appears that here x is the name of the property in the unnamed struct which is also bound to the value of x in its enclosing scope. This is a nice shorthand that I think would be valuable to keep in some form. It reminds me of how object shorthand in javascript works, which is really handy there:

() => {
  const x = ...;
  const y = ...;
  const z = ...;
  return {x, y, z}; // the same as {x: x, y: y, z: z}
}

As someone who uses generic-method chaining a lot (RxSwift, SwiftUI), this proposal looks very attractive.

I think, when some (most?) people hear "Anonymous Structs" the bulk of the use-cases they would imagine would be satisfied by plain tuples. But I most important ability here is to promote those "tuples" to conform to protocols. This is also a big convenience for protocols with auto-synthesized abilities such as Equatable, Hashable, Codable etc.

So I'm with Kevin_Lundberg here that the tuple syntax may be a good fit for this. Since this feature is not as useful if there are no conformances required then we can just promote tuples only when they are annotated as a particular protocol:

let equatable: some Equatable = (width: w, height: h)

One point I am still lost in is how to reconcile the implicit capture list for callables. Do we treat it like a member func with implicit self:

let equatable: some Equatable = (width: w, height: h, area: { self.width * self.height })

or as an independent closure:

let equatable: some Equatable = (width: w, height: h, area: { w * h })

We also need a new mechanism for mutable fields and mutating funcs. But following the original proposal's idea of fields being "computed properties", something like this may be close:

let equatable: some Equatable = (
    width: &w,  //  var width
    height: &h,  // var height
    area: { w * h }, // func area()
    setSize: mutating { (size: CGSize) -> Void in   // mutating func setSize(_:)
        w = size.width
        h = size.height
    } 
)

As for the issues on ExpressibleByTupleLiteral, the difference here is that callers wouldn't need to know the underlying concrete type of the some type, and is not initializable in any other way. So we wouldn't really need a fully-blown variadic generic type (correct me if I'm wrong).

2 Likes

What if you have more than one declaration in the same scope? I think we would also need some kind of discriminator.

As for alternative syntax like {{ ... }} I want to suggest to consider <{ ... }>, <[ ... ]> or <( ... )>, but only if we need a new syntax at all. Those look visually very interesting.

The reason why syntax is so similar to closures, is because these anonymous structs are closures - they capture values from the enclosing context, and they carry executable code. But compared to regular closures implementation is different - captured state is stored in-place and executable code is accessible though protocol witness table.

Also, being structs they can conform to other protocols is conformance can be generated by the compiler or can be picked up from protocol extension. This way you can have closures that are Equatable/Hashable/Codable.

4 Likes

I like the single curly brace syntax. I have a few question though:

Would you be able to make an anonymous struct, without putting it in a protocol type, and access the properties of it?

let a = {[x = 1, y = 1]}
print(a.x)

Would this be possible? Probably not, because some types are only implemented for the types of properties, subscripts, and return types of functions right now

let b: [some Hashable: String] = [ 
    {[x = 1, y = 1]}: "hello"
]

How about this though? Would this be okay, because it doesn't use some?

let c = [ 
    {[x = 1, y = 1]}: "Hello"
]

What if you have more than one anonymous struct, but in a single expression? Would it just complain that I try to put two different types as keys in a single dict?

var d = [
    {[x = 1, y = 1]}: "Hello",
    {[x = 2, y = 2]}: "World",
]

If the previous one is possible, would I be able to make new structs in a different expression that still fit in that dict?

d[{[x = 2, y = 2]}] = "goodbye"

Would the different instances made at the same point in the source code will be considered the same dynamic type?

func x() -> Any {
     return {[x = 1, y = 1]}
}
let e = x()
let f = x()
print(type(of: e) == type(of: f)) // true or false?

What if it's generated in a generic context? Probably not

func y<T>(type: T.Type) -> Any {
     return {[x = 1, y = 1]}
}
let g = y(type: Int.self)
let h = y(type: String.self)
print(type(of: g) == type(of: h))
3 Likes

Would you be able to make an anonymous struct, without putting it in a protocol type, and access the properties of it?

Why would you want this? You can do that with tuples already.

What if you have more than one anonymous struct, but in a single expression? Would it just complain that I try to put two different types as keys in a single dict?

Yes, here you have two anonymous structs in different code points, so they different types, even if structurally they look the same. But you can use different types in the same data structure if they are wrapped into existential containers.

Would the different instances made at the same point in the source code will be considered the same dynamic type?

Yes.

What if it's generated in a generic context? Probably not

Currently it is not possible to declare local structs inside generic functions - Could some types be allowed to be nested in generic functions?
. There are no strong reasons for that, it is just something not implemented yet. When it will be implemented local structs inside generic functions should behave as generic structs - if they are parametrised by the same types they are the same types, by different - different types.

1 Like

I was just wondering if anonymous structs would be as powerful as structs. It seems they won't be. There will be a lot of situations where you will be forced to use normal structs. In contrast the only place that you're unable to use anonymous functions is when you want to have argument labels (and it could be fixed in the future) Thanks for the answers!

2 Likes

But compared to regular closures implementation is different - ccaptured state is stored in-place and executable code is accessible though protocol witness table.

Which is one of the reasons I believe people will find the closure syntax confusing. Personally, the functionality benefits for me outweigh this drawback so I wouldn't mind having the current proposed syntax. But I'm not sure that can be said for most.

The quick style of initialization of anonymous structs reminds me of creating a class in Kotlin. That being said, maybe the { ( ... ) } syntax would be a good idea. Also, since properties would have their name and then an = operator, followed by their value, they would be different from tuples who use a : to separate their argument names from their values. This also avoids the possibility of the anonymous struct being confused for a capture list.

Also, I don't really think that there is any point in restricting the declaration of stored properties in an anonymous struct to within the ( ... ) part of it. I think you should be able to either succinctly declare it in the list at the top or in its body, as long as it meets the protocol requirements and all stored properties are given a value.

I was thinking that all of the following should all be equivalent (eq1, eq2, eq3, eq4, eq5):

protocol Foo {
    var x: Int { get }
    var y: String { get set }
}

var eq1: some Foo & Equatable = { (x = 4, var y = "Hello") }
var eq2: some Foo & Equatable = { (var y = "Hello", x = 4) }
var eq3: some Foo & Equatable = { (x = 4) in
    var y = "Hello"
}
var eq4: some Foo & Equatable = { (var y = "Hello") in
    let x = 4 
}
var eq5: some Foo & Equatable = {
    let x = 4 
    var y = "Hello"
}

I wasn't totally sure about whether or not to include in, but I think it makes sense if it is only required if the ( ... ) part of the declaration is either the only part of the declaration, or is empty (as shown in eq1, eq2, and eq5). Also, I see no reason to disallow (let x = 4), (private(set) var x = 4), or any other modifier for that matter as long as it satisfies the protocol requirements. I think that let should be implicit, but if it—or anything else that satisfies the protocol requirement—is provided, I don't see any reason for the compiler to complain.

Also, to make clear the separation between property names and values that share a common name, I feel like it would be useful to always require the property name and the value separated by = for each property. In the following example, though the property in the protocol shares a common name with a global variable, the property name must always be stated in the declaration of an anonymous struct (baz1, baz2, baz3, baz4, and baz5 would all be considered valid declarations under all of the aforementioned rules):

protocol Bar {
    var a: Int { get }
}

var a = 24
let baz1: some Bar = { (a = a) }
let baz2: some Bar = { (private(set) var a = a) }
let baz3: some Bar = { (var a = a) }
let baz4: some Bar = { (let a = a) }
let baz5: some Bar = { 
    let a = a
}
let baz6: some Bar = { (a) } // Error: a value must be provided to each stored property of an anonymous struct.

Grammar:

declaration-headattributesopt declaration-modifiersopt var
declaration-headattributesopt declaration-modifiersopt let
declaration-headattributesopt declaration-modifiers
declaration-headattributes

property-nameidentifier

anonymous-struct-property → declaration-headopt property-name type-annotation = expression
anonymous-struct-property → declaration-headopt property-name = expression

anonymous-struct-property-list → anonymous-struct-property | anonymous-struct-property , anonymous-struct-property-list

anonymous-struct-property-declaration( anonymous-struct-property-list )

anonymous-struct-declaration{ anonymous-struct-property-declaration }
anonymous-struct-declaration{ anonymous-struct-property-declaration in struct-body }
anonymous-struct-declaration{ struct-body }

I think that syntax even though looking okay-ish would eventually create some parsing difficulty with closure parameters. I could be wrong, but I highly suspect this.

2 Likes

I didn’t even think of that. Maybe you’re right. I don’t have the compiler knowledge to back up this claim, but wouldn’t the use of the equal sign be sufficient to disambiguate it from closure parameters?

Possibly, but you don‘t always need it as shown in the original post. { (a,b) in }, this would be a valid syntax for a (A, B) -> Void closure and your syntax form, which would be ambiguous unless there is more type information provided for the compiler through an explicit closure type or an expected struct.

Oh okay, I see what you mean, I meant to say in my post that I think that the equal sign should be required in order for there to be a separation of parameter names from their values. For example, if you had the following, the equal sign would be required to separate the two entities:

protocol Bar {
    var a: Int { get }
}

let a = 24
let baz: some Bar = { (a = a) }  // The `a` on the left is the property name, while the `a` on the left is the value of the global variable `a`

I will clarify this point further in my previous post.

At that point, just go write a struct :man_shrugging:

Personally, I think this proposal has been far overdone, and introduces a lot of unfamiliar syntax while failing to persuade me that this is an acceptable cost for a useful or desirable feature. If somebody starting writing things like the above in my codebase, I would not be happy.

As for the compiler: I see no reason why the compiler is prohibited from specialising functions by their closure parameters the way it specialises by generic type parameters. If there are reasons, the proposal does not do a good job explaining them or how this proposal solves them, IMHO.

12 Likes

My example was elaborate, but this reduces a lot of unnecessary "temporary" symbols just to conform to a protocol.

Consider this (I'll use the original proposal's syntax):

api.put(
    endpoint: "/profile",
    jsonBody: JSONEncoder().encode(
         { [target = user.id, image = user.photo.url] } as some Codable
    )
)
api.put(
    endpoint: "/profile",
    jsonBody: JSONEncoder().encode(
         { [target = user.id, name = user.name] } as some Codable
    )
)
api.put(
    endpoint: "/profile",
    jsonBody: JSONEncoder().encode(
         { [target = user.id, phone = user.addressBook.phone] } as some Codable
    )
)

Today you'd need 3 separate structs for each pattern.

Terms of Service

Privacy Policy

Cookie Policy