Thank you for stating this. I personally found trying to digest this thread exhausting.
To the original poster: If future replies are concise, participation by other members of the community will be much easier.
Thank you for stating this. I personally found trying to digest this thread exhausting.
To the original poster: If future replies are concise, participation by other members of the community will be much easier.
Honestly, I find a lot of repetition across replies, but not within them. Perhaps the question should be put to others: why does the OP have to reply over and over again to the same objections?
In an attempt to steer away from the negativity in this thread, I want to say that I really appreciate the work you have put into this @miku1958. You are definitely solving a real-world problem here.
For me, typed throws use case has not yet come up, but I see the value there. In my work "union types" are used very commonly and are very powerful tool for network API contract. Using generic OneOf# types is not very convenient, but it gets the job done.
I also wonder what the right expectation is for a pitch author here. It seems hard to respond to every alternative design suggestion unless it engages with the tradeoffs and solutions already described in the pitch.
I would expect that A | B would be a non-nominal type and would, therefore, not conform to Codable anyway (tuples don't either).
Again, if it's a non-nominal type, you wouldn't be able to extend it in this fashion.
We run into broadly similar problems with typealiases here, and I don't think it's a stretch to say that a sugar type would have some extra rules applied before it gets mangled. I don't pretend to have thought through the entire problem space, but this doesn't strike me as insurmountable, especially if we clearly define the rules for how the types are sorted. As well, if that sorting occurs early enough, then the latter stages of compilation won't even need to care about B | A.
(That said, generics over sum types could pose deeper problems.)
For what it's worth, that already happens:
typealias B = A
func f(_ a: A) {}
func f(_ b: B) {}
func g(_ x: T?) {}
func g(_ x: T!) {}
func g(_ x: Optional<T>) {}
func g(_ x: Swift.Optional<T>) {}
func g(_ x: Swift::Optional<T>) {}
Indeed it does, and for what it's worth I'm fine with that. ![]()
I'm not sure I follow. We already have syntactic sugar for Optional, Array, etc., and typealiases too—I would expect type canonicalization to take sum types into account at the same layer it does for those cases.
Spelling is not identity in Swift.
Edit: It slipped my mind, but I forgot we already do this exact sort of reordering dance elsewhere in the system anyway:
let x: T & U = ...
let y: U & T = ...
assert(type(of: x) == type(of: y)) // succeeds
I wonder if, for such larger proposals that are likely to consume a ton of community resources, the proposer should show up to one of the workgroup/steering group meetings and discuss the strategy around it. That way, the group that's likely to have a hand in finally approving the proposal can provide an early feedback and ensure the community member truly is a human with all this necessary knowledge in their head, not just someone prompting LLMs.
Not making an accusation, in fact this would allow more LLM usage without DDoSing the entire community with walls of text.
There needs to be some proof-of-knowledgable-human in the early stages of massive proposals. Joining a community meeting is just one idea, maybe there are others that can't easily be gamed with more LLM tooling.
While we do not (yet) have a policy about AI tooling, if your reply to genuinely thoughtful human feedback contains raw LLM output like the following, it is a problem and you will get pushback, because you have transformed a forum for humans to engage in discussion with each other to an ersatz UI for prompting Claude with a go-between:
Earlier, you claimed that Claude's role here was simply to simplify some underlying longer text. This is flatly contradicted by the walls of LLM-generated text that have followed which, clearly, show that brevity is not even the slightest consideration.
The point of my earlier intervention was to point out that the foundations of the proposal have telltale AI hallucinations; doubling down by taking genuinely thoughtful human feedback back to Claude and reposting its barely warmed-over output shows that you (the human) are not in the driver's seat.
It goes without saying that a proposal which does not have a human author standing behind its totality can't move forward, and it is bluntly a waste of the community's attention and time to continue in this manner. This is in no small part because the point of discussion is to develop your understanding: plugging the discussion into an LLM with a 1M token context window develops nothing.
I'm not trying to complicate or block the review of this proposal. On the contrary, I'm suggesting that we make it more compact and focused, with a clear, well-argued motivation.
The motivation for typed throws is crystal clear:
typed throws is about error handling — something we write a lot of and deal with daily.
As for the other scenarios:
Either / OneOf solved the problem just fine._ConditionalContent directly.Either to store a collection of values of two types turned out to be the result of poorly thought-out design. There may be real-world use cases where Array<Int | String> is genuinely necessary, but in my experience, such suggestions have come from people with a background in other languages who don't yet have a strong grasp of Swift idioms.I'm concerned that as soon as this feature is added to Swift, we'll be flooded with ill‑considered APIs that return things like String | Int .
Take Result as an example: it could theoretically be expressed as (T | E: Error) , yet I don't see any benefit in using that syntax – on the contrary, I see strong reasons to stick with Result .
My worry is that introducing union types will encourage a style of coding that feels more like JavaScript than idiomatic Swift, leading to poorly designed code.
(Separate replies for separate concerns.)
I take it from this:
Round-trip property: data goes back out the same shape it came in.
and similar remarks that you think of "the round-trip" as primarily about data->Swift->data. I'm concerned with the other round-trip: Swift->data->Swift. I would find it very surprising to store my [Point2D | Point3D] somewhere, load it back, and get something different to what I stored.
I feel personally that the safe default (in the non-technical sense, just "doesn't make it easy to make a disastrous error") is more important than the convenience argument you're making with:
That's load-bearing for interop with non-Swift JSON producers, which is the dominant Codable use case.
I don't understand the technical proposal anywhere near well enough to see how a tagged Codable synthesis would work, or not work, with the implementation. If the answer is "it's technically not feasible", to me that would suggest not synthesising Codable at all, but putting the effort into making sure from v1 that a user-supplied conformance is possible.
I didn't intend to make an accusation, and I'm personally not bothered by the use of LLMs per se. Indeed if that's been effective for you in creating the prototype implementation, that's really wonderful!
There is a difficulty here though that I'm trying to express constructively, somewhere closely adjacent to the phrase "walls of text". The technical implementation is beyond my grasp. The proposal text itself is too long for me to easily engage with. I'm looking for core concepts that I can grasp, as a first step into understanding the wider proposal. And unfortunately some characteristics of LLM text generation get quite directly in the way of doing that: they don't necessarily summarise accurately (which is essential for the core concepts), and they (like me) run long by default. When the early revisions retract precisely those core-concept explanations, I feel diminished trust in whatever is left: this is where the worry about "epistemic grounding" comes in.
As a constructive suggestion, I personally would be helped more if you put your effort into concise explanations of how the proposal does its work, at a conceptual rather than compiler level, and kept them clearly separate from the motivation. Taking the example of the Codable synthesis: it appears we simply disagree about the relative importance of some usage patterns compared to others, which is not very interesting, but I would be very interested to better understand what Codable-synthesis options are easy, difficult, and impossible given the approach you've taken.
As a moderator, I've been trying to use a light hand here, but I do think I need to step in, because the conversation has clearly gone sideways a bit.
The Core Team is still working on an official LLM policy, but I expect that it will allow people to use LLMs to help with contributions (which includes posts here) as long as they take personal responsibility for the result. I don't think there's an inherent problem with LLM assistance in writing forum posts, but I do think there are inherent risks that people doing it need to be thinking about as part of their personal responsibility.
The first risk is that the LLM will launder misinformation. It is always tempting as a writer to say something that sounds convincing but which you can't actually back up, just to try to win an argument. As humans, we usually see that as a form of lying, and that alone stops most people from taking it too far; and when we do do it, it's often discovered, with real costs for our personal reputation and pride, and the fear of that also holds people back. But LLMs do not have any sort of semantic model of truth or evidence, nor do they have moral leanings or concern for their reputations; it is well-known that LLMs will often confidently state things that have no foundation at all. And when an LLM says something that sounds convincing, it is much easier psychologically to leave that in unquestioned than it would be to write it oneself.
The second risk is that the LLM can generate text much faster than a human can. Even human conversation can sometimes fall victim to imbalances. Most of us have probably had professors who rambled their way through their long-familiar lectures while their students struggled to really process anything they were saying. (I have a distressing tendency to do this even in personal conversations.) Still, the fact that it's a human who's writing or speaking forces a certain level of proportionality. In fact, if I'm really thinking about my words carefully, it often takes me much longer to say something it than it does for someone to understand me. LLMs do not have this constraint; an LLM is a program that generates text, and it will keep generating text as long as you ask it for more, and by the magic of massive parallelism it can do that really fast. The result is that it's quite easy for an LLM to dominate a conversation, at first by generating so much text that everyone else is spending all their time just trying to keep up rather than responding, and then by attrition, as people stop trying to keep up because they no longer see any value in participating. This is not respectful of other people's time.
So I think it's incumbent on people who use LLMs for posting to always be carefully and rigorously reviewing everything the LLM writes, both for accuracy (paying special attention to claims that you might not fully understand) and for conciseness and non-redundancy (trying not to over-burden everyone trying to engage with the thread). The result will probably be that the LLM will never actually save you time; a theoretical you who was just as capable of writing that post could probably have written it faster. The value of the LLM is that it might help you write posts you couldn't have written before: you will learn something new with the help of the LLM, which now you can bring to bear in the conversation.
I wonder the same.
Along the same lines... with optionals we have "optional promotion", couldn't we have the similar "enum promotions" for normal enums?
extension MyEnum: EnumPromotable {}
let v: MyEnum = 42 // .int(42)
let v: MyEnum = "s" // .string("s")
let v: MyEnum = 1.0 // error in case of ambiguity, e.g .double(1.0) vs .float(1.0)
Very well put: I would hasten to add that the value-add here is necessarily circumscribed by the earlier point that LLMs can say things which sound convincing but are ultimately unsupportable. Therefore, for this essentially didactic use case, their benefit lies in a (potentially narrow) window where the LLM surfaces something which you would not have thought of otherwise, but which once surfaced you are capable of internalizing and evaluating the truth of.
Massively parallelized generation of ideas, even if they turn out to be brilliant, will inevitably run up against the human limitation that if you didn't know anything about these ideas moments ago, it's not always clear how you're going to be well placed to evaluate their veracity just moments later. So, adhering to the exhortation to "learn something new . . . [and then] you . . . bring it to bear in the conversation" can be uncomfortable, because it also entails recognizing when the AI-generated output might be potentially brilliant and useful in someone else's hands but out of your own reach.
I don't think this would be good, but I also don't think optional promotion is good, so my opinion is probably wrong.
However, the concept of enumerations with associated values being represented better as a constrained collection of aliased types would be wonderful. Enum cases are not properly represented as the typealiases for their associated values that they actually are (in every case but the labeled 1-tuple, which is not representable in the type system).
Narrowed Any should be the same as this, but with the casting to types, rather than cases, as seen above.
enum E<T0, T1> {
case t0(T0)
case t1(label: T1)
}
let v: E<Int, _> = .t1(label: "🏷️")
v as? .t0 // nil
v as? .t1 // "🏷️"
Have to say, I've read these replies carefully and what I have been struck by is the extent to which every reply is grounded in code. Listing particular changes required, algorithms used, comparisons with other languages et al, is in my opinion, is the best way to demonstrate the needed architecture and the changes it requires. I'm finding @miku1958's writing through this entire thread remarkably informative.
This proposal is very different from others in that it is much more ambitious. That does not seem like a bad thing to me.
It is clearly written by an LLM. And there is legitimate concern that the OP doesn't really understand what he's posting. I think it's a very difficult situation to navigate. On one hand, we don't want Swift taken over by LLM output, and on the other, an LLM can allow someone who is not a computer science expert to make real contributions, with the caveat that they won't really understand their own proposals.
There's something to be said for letting the work stand on its own merits, but communicating with an LLM instead of a person who has their own ideas and perceptions makes for either a one-sided or circular conversation, in which nothing is resolved.
Is the Core Team open to engaging with the community in the development of this policy?
I'm 100% in support of Dmitriy's position here:
While I'm tempted by the simplicity of writing func foo(x: String | Int) I don't think it's good long term.
I've come to think of tagged enums as Swift's greatest feature, possibly even more so than Swift's amazing type system. I actually like that we don't have anonymous structs, classes or enums even though I'd love to write them sometimes ( GitHub - jjrscott/JavaScriptTranspiler: A JavaScript to Swift converter · GitHub would certainly be easier).
Looking at the example below, as a reader I have no idea what t0 or t1 are:
enum E<T0, T1> {
case t0(T0)
case t1(label: T1)
}
Contrast that with my work on porting certain algorithms to Swift (eg LZW without pre-populated string table / alphabet · jjrscott ) where one can see how I was able to articulate the algorithms in a concise way I don't think even the original writers could.
For example, here's the difference between LZ77 and LZW:
struct LZ77<T : Equatable> {
enum Token {
case literal(T)
case range(Range<Int>)
}
func encode(input: [T]) -> [Token<T>]
}
struct LZW<T : Equatable> {
enum Token {
case literal(T)
case index(Int)
}
func encode(input: [T]) -> [Token<T>]
}
With this proposal I could write the following:
struct LZ77<T : Equatable> {
func encode(input: [T]) -> [T | Range(Int)]
}
struct LZW<T : Equatable> {
func encode(input: [T]) -> [T | Int]
}
which IMHO does not describe the algorithm, nor do I know what would happen if I tried to encode a Range<Int> or an Int.
If tuples could be either with labels or without labels perhaps this new types could be as well:
func encode(input: [T]) -> [(literal: T | range: Range(Int))]
func encode(input: [T]) -> [(literal: T | index: Int)]
Nor should you; those names are not for reading. That enum is just homomorphic to what the real solution needs to be.
Your example makes the assumption that enumeration cases are necessary to carry information, but the same could be done with types, if the compiler knew the complete set of possible types.
As it is, removing "strong type aliasing" with that technique is cumbersome, doesn't offer exhaustivity of enums, and results in "wide" Any:
protocol LZ77Token<Value> {
associatedtype Value
var value: Value { get }
}
struct LZ77TokenRange: LZ77Token { let value: Range<Int> }
struct LZ77<T : Equatable> {
struct TokenLiteral: LZ77Token { let value: T }
func encode(input: [T]) -> any LZ77Token
func f(encoded: any LZ77Token) {
let value = encoded.value // value is Any
But if narrowed Any supported named aliases, which maybe could be done with syntax as simple as this…
struct LZ77<T : Equatable> {
typealias Encoded = (literal: T | range: Range<Int>)
func encode(input: [T]) -> Encoded
…then erasing would be much better, perhaps doable with just |—(_|_) if that can't work:
func f(encoded: LZ77<Bool>.Encoded) {
let value: | = encoded // value is (`Bool | Range<Int>)
encoded as? .literal // Bool?
encoded as? .range // Range<Int>?
There will always be cases where an actual enumeration-with-all-cases-being-associated-value-type-aliases will be useful for typing, instead of what the equivalent alias would be. But these are rare. What's proposed here would allow for many, many enumerations to be type aliases—and most of those, anonymous ones.
I concur entirely with your summary that this proposal will essentially give Swift anonymous enums with anonymous cases.
I also really want this, but I think we should not have it. For the reasons Dmitriy and that, in my view, enums are a fundamental advance of which Swift should rightly be proud.