Here, the "nice little emergent feature" I'm referring to is the ability to Optionals to compose into nested optionals (Optional.some(Optional.some(something))), and its utility in modelling the distinction between "missing" and "present but nil".
From what I understood of your original response, you're suggesting that type unions (or "non-nominal enums") could also do that, but I just don't see how.
You would have to forbid associativity of | in order to achieve that result. Mathematically (T | Nil) | Nil is equivalent to T | Nil | Nil which is equivalent to T | Nil, but what you're really trying to express with (T | Nil) | Nil is that (T | Nil) is the .0 element and Nil is the .1 element of this anonymous thing.
I'm suggesting that this is enums, but without a name. A suggested spelling:
(first | second)
// enum AnonymousEnumWithoutAssoicatedValues {
// case first, second
// }
(first: Type | second)
// enum AnonymousEnumWithAssociatedValues {
// case first(Type)
// case second
// }
(Type | AnotherType)
// enum AnonymousEnumWithoutLabels {
// case 0(Type)
// case 1(AnotherType)
// }
(Type | (AnotherType | none))
// enum NestedAnonymousEnum {
// case 0(Type)
// case 1(Nested)
//
// enum Nested {
// case 0(AnotherType)
// case none
// }
// }
But I can also envision other spellings.
However, the feature set it the same as for enums, except that they cannot be extended or have non-case members â just like unnamed tuples correlate to nominal structs without extensions, etc.
How would compiler know if second is a case and AnotherType is a type? Do you suggest to do a lookup and if the type is found then it's a type? I don't think we have precedents like that, so I guess we'd like to have some different spelling to distinguish these two cases, bike shedding:
Sure, the spelling probably has shortcomings. I just wanted to explain how a non-nominal enum is both useful, would fill a gap, and could work as a kind of union type. But I'm sure the spelling has room for improvements.
Since this is anonymous sum type in contrast to anonymous product types (tuples), we can use brackets like syntax, so that we can have analogous syntax. If you use | as bracket, then you can write something like this.
// equal to enum {case 0(T), 1(U), 2(V)}
// like (T, U, V) is struct {var 0: T, 1: U, 2: V}
|T, U, V|
// equal to enum {case first(T), second(U)}
// like (first: T, second: U) is struct {var first: T, second: U}
|first: T, second: U|
// enum {} and can be alias for `Never`
// like () is `Void`
||
// unanalogous but equal to enum {case none, some(T)}
|none:, some: T|
And this can be naturally extended for variadic generics;
(nope() | opacity(Float) | Color | rgb(Int, Int, Int))
or something else for outer brackets and inner separator.
The idea is to use empty parens to denote cases without associated types (like nope() above) and use bare name to denote types (like Color above).
use site usage:
func foo(_ v: (nope() | opacity(Float) | Color | rgb(Int, Int, Int))) {
switch v {
case .nope: // or case .nope():
case let .opacity(value): // or case .opacity(let value):
case let value as Color:
case let .rgb(r, g, b): // or case .rgb(let r, let g, let b):
}
}
right now in some code that deals with the swift @available mixin, i have three âunionâ enums that represent the range of versions that an API is deprecated for:
extension Availability
{
@frozen public
enum EternalRange
{
case unconditionally
}
}
extension Availability
{
@frozen public
enum AnyRange:Equatable, Hashable, Sendable
{
case unconditionally
case since(SemanticVersionMask?)
}
}
extension Availability
{
@frozen public
enum VersionRange:Equatable, Hashable, Sendable
{
case since(SemanticVersionMask?)
}
}
this is because only some deprecation ranges make sense for various availability domains:
extension Availability
{
@frozen public
enum AgnosticDomain:String, CaseIterable, Hashable, Equatable, Sendable
{
case swift = "Swift"
case swiftPM = "SwiftPM"
}
}
extension Availability.AgnosticDomain:AvailabilityDomain
{
public
typealias Deprecation = Availability.VersionRange
public
typealias Unavailability = Never
}
extension Availability
{
@frozen public
enum PlatformDomain:String, CaseIterable, Hashable, Sendable
{
case iOS
case macOS
...
}
}
extension Availability.PlatformDomain:AvailabilityDomain
{
public
typealias Deprecation = Availability.AnyRange
public
typealias Unavailability = Availability.EternalRange
}
and so forth. i feel like all these {Eternal, Any, Version}Range types are just a workaround for a lack of anonymous union types.
This would probably solve the issue for String, but this can only be part of a good solution. But thank you for the hint.
Thank you for the hint, I will evaluate this, maybe this is the solution I need. [UPDATE: This cannot cope with the recursive nature of some of the things I would like to use via my result builder, but may help with some edge cases.]
âŚSo maybe I get this resolved, but I have the feeling that something âmore easyâ, âmore straightforwardâ is missing here and that some kind of union types (as an enhancement to enums?) is missing here for a more natural way to cope with such cases (although I have to confess I didn't think it through in any systematic way).
The crucial point here is that the idea for my problem (and I suppose for many similar problems) is âthose are the types I want to let people put in hereâ (without the syntactical âornamentsâ needed for enums), and I would like to express this in a clear and easy way, but what I need is knowledge about the special structure (result builders) used which is not really a clear expression of intent.