Anonymous Structs

Wouldn’t that be single struct with optional value?

1 Like

I was really on the fence about this proposal, but I do kind of like this example.
Still not sure if it's a good idea but this at least seems like a reasonable use case to me.

You assumed that all of the properties were Strings. What if each of those fields are their own Codable types?
The proposal also mentioned @propertyWrappers support as well. So when Codable support for those get approved we may also be able to do

{ [target = user.id, @ISO8601Date var birthday = user.dob] } as some Codable

I mean like this:

struct ProfilePatch {
  let target: ...
  var image: Image?
  var name: Name?
  var phone: PhoneNumber?
  var birthday: Date?
}

Without using the anon-struct. If anything, I see you example in particular as a misuse of anon-struct.

Can you elaborate at all on what the advantage is of using a callable anonymous struct in place of a function or closure? When would this be useful?

I just used arbitrary fields. They don't have to have anything in common. The point is I can write any pattern I need to conform to a protocol without having to declare a struct I will never use anywhere else.

I find the example of ad-hoc codable compelling. I’m not so sure that it generalizes well to anonymous structs and arbitrary protocols.

Maybe a good solution is that tuples of Codable types are themselves codable by default.

1 Like

I think another interesting case is Hashable. I can imagine cases where you want to key a dictionary on 2 or 3 fields, but don't wish to formalize the type that is only used for this purpose.

This is good feedback, thanks. I'm going to work on improving the examples.

Yes, your feedback has been very helpful. Thank you!

Allowing tuples to conform to protocols would be very useful, but is orthogonal to this proposal. In particular, this proposal focuses on cases where you need to provide an ad-hoc conformance. A generic tuple conformance isn't able to do that.

Yes, it is captured by value in a stored property named x.

This is a typo. It should read var y = x + 1. Thanks for pointing it out.

This expression is used to initialize the stored property. Your understanding of the synthesis is close, but not quite correct. let would be used, as you wondered. var is only used when it is specified in the capture list.

Why do you think this? The requirements are matched based on the names of the synthesized properties (which are determined by the names of the captures). Order of the requirements is irreverent.

It looks like my typo caused your confusion. Your much clearer version is what I had intended to write. Appologies for the confusion and thank you for taking time to think through it anyway.

2 Likes

You're correct, Sean, not a downer. This proposal would make reading Swift dramatically more difficult by overloading trailing closure syntax.

Consider for a moment that naming one-offs has benefits as well. Naming variables improves readability. It also can help writers reason about their work.

Even with another syntax, an issue seems to be that a method could have the same base name but take two different struct types. If those struct types have the same parameter types, the compiler will be unable to disambiguate.

"Anonymous Structs" already exist as tuples, to paraphrase @JohnEstropia.

Outside of SwiftUI, @anandabits's proposal has even less utility. The entire proposal reeks of a solution in search of problems.

3 Likes

Can you clarify what you are asking about here? I'm not quite sure what you're asking.

@DevAndArtist My sense is that most of the feedback has indicated that the syntax should differ from closure syntax so we might as well bikeshed if people want to do that. There are a bunch of options and I don't have a good sense of what would be best. I tend to think braces should remain the outer delimiter though. For example:

let eq: some Equatable & Foo = {( [x = a, var y = a + 1] )}
let eq: some Equatable & Foo = {| [x = a, var y = a + 1] |}

(note: I haven't looked into whether these options would be viable to parse unambiguously)

Why are you suggesting ideas using angle brackets?

1 Like

I don't think so. If this was supported it would have to be equivalent to some Any. But no protocol conformances would be synthesized so the only way you could do anything with this type would be to use reflection to access the stored properties. I can't think of any good use cases for that.

You left off the type annotation on the dictionary here, which would be necessary with anonymous structs. As @Nickolas_Pohilets mentioned, each anonymous struct would be a distinct type. So you would need to use an existential type context. In a hypothetical future version of Swift would would be able to do this:

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

The primary limitations of anonymous structs relative to nominals structs are that you can't name the type and they therefore can't be instantiated directly (or more generally, static members can't be used directly). They are only instantiated at the point of the anonymous struct expression, code contained in that expression which uses Self to name the type, and in generic code which uses initializer requirements. Similarly, static members are only usable in code contained in the anonymous struct expression or in generic code which uses the static member requirements.

The original example all points to /profile endpoint, so anyone somewhat familiar with it would internalize that they have some commonality. That'd have all of the disadvantages of anon-struct, while having little of the advantages. You essentially just triple the typo surface area just like that. And if you want to include any combination of the data, birthday + phone for example, it quickly explode exponentially. Hence my reply.

Anyhow, lets say they have nothing in common, and not even pointing to the same endpoint. They still have the problem of relying on the external convention (web API in this case), and so any usage deep within the code will risk being missed when the external convention changes, as oppose to putting all the external codable in one place. It still reeks of technical debt.

IMO the usage of anonymity is useful when only internal consistency is required, and the usage is extremely local, like hash of internal dictionary, the codable of internal data, etc.

2 Likes

Just from the visual perspective those forms look appealing to me.

The primary motivation for this proposal is not about specialization or optimization. Closures are reference types. This proposal introduces a value type alternative. Closures also cannot conform to protocols. Even if functions could conform to protocols, highly desired conformances such as Equatable cannot be provided in general - they would be context-dependent. There is no good way to express that context dependence in the language. I will beef up the motivation with a discussion of this.

Further, this proposal introduces the ability to provide concise ad-hoc conformances to arbitrary protocols. Sometimes these conformances will have more than one requirement, as in the Monoid example. A single function is not alway sufficient. We shouldn't have to resort to a fully written out type all we need is a simple ad-hoc conformance.

1 Like

I may have misunderstood, but if I have:

let x = 0
let eq: some Equatable = { [x, var y = x + 1] }
let eqAnother: some Equatable = { [a, var b = x + 1] }

then they both can’t desugar to a struct with the same name (_Anonymous), right? I suppose we would have to suffix a number to it (like _Anonymous1, etc). It’s just that it wasn’t stated in the proposal text.

My team uses local Codable structs for request body payloads often. This is a great example! Thank you for posting it! I have good news for you: the as some Codable syntax is not necessary because JSONEncoder.encode already provides the type context you need.

Did you see the followup example which used a hypothetical future Swift where Codable synthesis supports attributes to configure the encoding?

jsonBody: JSONEncoder().encode(
    { [target = user.id, @ISO8601Date var birthday = user.dob] }
)

This demonstrates the importance of ad-hoc conformances and why conformances for tuples are not sufficient to address the use cases targeted by this proposal.

This one is trickier. I discussed it upthread. But to be honest, this use case is better motivation for tuple conformances than anonymous structs.

1 Like

The actual name used would be an implementation detail. The only guarantee is that it would be unique. _Anonymous was just used for the purpose of showing what the generated struct looks like. I'll update the proposal to make it clear that the actual name _Anonymous is not used.

2 Likes

While I haven't given this much thought, it seems to me that most use cases could be implemented with tuples, if only tuples could conform to protocols, non? If we imagine that tuples of codable/equatable/hashable are themselves codable/equatable/hashable, would we still need this proposed new syntax?

If not, what are the reasons to pursuit this pitch, over the protocol conforming tuple path?

4 Likes

I think @Nickolas_Pohilets captured the intent well:


I completely disagree. Closures already are value types because their context is immutable—if they capture mutable variables, they do so by reference, but the references themselves are immutable, just like referencing an object inside a struct. The primary motivation I see for this feature is to be able to unbox closures, and be able to extract conformances for Equatable/Hashable/Codable from the closure's contexts. We already have "anonymous structs" in the form of tuples otherwise.

7 Likes