Would the requirement to cast in some way before being able to use a value not count as explicit projection? (I might be mixing out projection/injection? Also I'm assuming wrapping a value in a union type implicitly would not result in major type-checker problems?)
Great ideaš. Having Either<First, Second> Type in Standard Library allows us to use the same type across libraries. Similar situation was with the Result<Success, Failure> Type.
Either type is very useful with UDF / Rx / Combine and other cases. Some examples:
let completion: (Result<Either<AuthAgreement, ActiveLogin>, Error>) -> Void
func eitherRequest<L, R>(_ target: Target, completion: @escaping NetworkCompletion<Either<L, R>>
let quizScoreViewModel: Either<ScoreViewModel, String>
let quizAnswer: Either<String, [QuizAnswerVariant]>
let showError: Either<ErrorVM, TextSheetVM>
Our implementation:
/// Either represents duality, a value that can either be of a type or another
public enum Either<L, R> {
case left(L)
case right(R)
}
extension Either {
public var leftValue: L? {
switch self {
case .left(let leftValue): return leftValue
case .right: return nil
}
}
public var rightValue: R? {
switch self {
case .right(let rightValue): return rightValue
case .left: return nil
}
}
}
extension Either where R: Error {
public func asResult() -> Result<L, R> {
switch self {
case .left(let leftValue): return .success(leftValue)
case .right(let rightValue): return .failure(rightValue)
}
}
}
extension Either where L: Error {
public func asResult() -> Result<R, L> {
switch self {
case .left(let leftValue): return .failure(leftValue)
case .right(let rightValue): return .success(rightValue)
}
}
}
extension Either: Equatable where L: Equatable, R: Equatable {}
extension Either: Hashable where L: Hashable, R: Hashable {}
extension Either: Decodable where L: Decodable, R: Decodable {
public init(from decoder: Decoder) throws {
do {
let decodedInstance = try L(from: decoder)
self = .left(decodedInstance)
} catch let leftError {
do {
let decodedInstance = try R(from: decoder)
self = .right(decodedInstance)
} catch let rightError {
let info = "Type: \(Self.self), \n left: \(String(reflecting: leftError))\nright: \(String(reflecting: rightError))"
throw JsonMappingError(code: .typeCastFailed,
userInfo: ["EitherMappingFailed": info])
}
}
}
}
If we're going to push a simple Either
type then I would prefer the first
and second
case names as they almost mirror the index based names of a potential variadic OneOf
enum, except that they don't start with zero
. Ultimately I still would prefer OneOf
(but maybe with a different/better name) and would stop using Either
immediately, even to represent duality.
To everyone discussing union types, subtyping and implicit conversions: please stop. The core team has made it abundantly clear that they do not believe this feature is a fit for Swift. They have provided very good reasons for this. Discussion in this direction is not helpful and distracts from discussion of directions that are viable and fill gaps in the language.
Please donāt mix people who continue trying to push union types with those of us who are focused on trying to discuss structural sum types. Yes, a lot of people are attracted to the idea of union types. But that is not an argument against structural sum types. It demonstrates that people continue to need to be educated about why union types are not a fit for Swift.
The problem with Either
is that it leaves us with either clunky nesting or our own OneOf3
, OneOf4
, etc. Either
is a narrow solution. Many of the use cases I run into are in cases of composition which are best designed as variadic. The language should provide a solution that works in this context.
Please letās not. Letās focus on creating examples that demonstrate why structural sum types are a better approach.
Actually, I haven't even seen a convincing argument not to add support for |
at type levelā¦
Although I agree that a dedicated, unspecific Either
-type isn't a good addition, that is what the thread starter wants to discuss here ā and imho we should respect this request.
For a huge and complicated pitch, it might make sense start collecting reasons for dismissal upfront ā but for a simple thing like enum Either<First, Second>
, imho it is fine to focus on improvements and wait with negative feedback until review.
as?
could be used as the projection syntax, yes, and I believe that would not have particularly severe type-checking problems.
"Projection" was probably not the best word-choice here on my part; I just meant in general getting at the underlying values.
as?
would still have the ambiguity problem when more than one tag in the sum has the same payload type, though.
Although I agree that a dedicated, unspecific
Either
-type isn't a good addition, that is what the thread starter wants to discuss here ā and imho we should respect this request.
I completely agree. This thread has been overwhelmed by discussions of counter-proposals.
I completely agree. This thread has been overwhelmed by discussions of counter-proposals.
Would the core team prefer the community to start separate threads every time there are different fundamental approaches to solving the same problem? We could certainly do that, but what would the advantages be? Keeping the discussion in a single thread gives participants with different views more opportunity to interact, which I think is a healthy thing.
I don't see a whole lot of interaction with the original proposal in here. It's 90% people discussing the language design of structural sum types. The OneOf
I'm seeing described is essentially the same; it's not implementable without compiler magic because the cases somehow get different names.
Although I agree that a dedicated, unspecific
Either
-type isn't a good addition, that is what the thread starter wants to discuss here ā and imho we should respect this request.
Thank you for understanding, but I should clarify something: Iām not against anything other than Either - after all thatās what discussion is all about. Nonetheless, I appreciate implementable proposals.
The possibility of adding OneOf, OneOf2... is worth exploring, but we should also consider what would happen when adding a variadic OneOf. Source might not break, but ABI might - Iām not really familiar with ABI stability so please correct me if Iām wrong. Also will library authors be willing to switch to the variadic OneOf once it comes out, or will OneOf3 become a typealias if itās variadic counterpart? I think these are valid questions worth exploring when discussing currently implementable designs.
as?
would still have the ambiguity problem when more than one tag in the sum has the same payload type, though.
Yes. In a generic context where types may be identical, it would create nasty particular cases:
struct Stuff<Left, Right> {
var value: Left | Right
var left: Left? { value as? Left }
var right: Right? { value as? Right }
}
let stuff = Stuff<Int, String>(value: ...)
assert((stuff.left == nil) != (stuff.right == nil), "Easy, right?")
let stuff = Stuff<Int, Int>(value: ...)
assert((stuff.left == nil) != (stuff.right == nil), "Oh, well...")
Yes, you're absolutely right. I was analyzing it syntactically and not thinking about the semantics.
m starting to lean towards providing an
Either
in the library. Defining a realenum
would usually be the better choice for an API, just like defining a realstruct
is usually better than using a tuple, but it's very convenient in implementations to have a type at hand with the right conformances and so on. The downsides of the type (just two cases, meaninglessfirst
/second
labels) probably provide the right push towards realenum
s.
In my opinion, tuples provide significantly more value than an Either struct would.
In addition to the common problems with tuples that Either would need new or breaking API when you move to a named type, Either would not have the option to provide semantic naming to the individual values of a particular usage.
Tuples do not provide encapsulation or association of logic - I cannot add methods to my (first: String, second: String) tuple like I can a struct. Either on the other hand would provide a mechanism for extensions to be used to attempt to carry forward the use of the Either type to library and application specific use cases.
In this way, I am concerned that the little bit of benefit of ease of use with either-vs-enum (in that it fixes a very narrow need of reducing typing when you quickly need a two option tagged union) encourages significantly more accumulation of tech debt in the resulting library than tuples-vs-structures as a comparative feature relationship.
In my opinion, tuples provide significantly more value than an Either struct would.
Iām assuming you are talking about Union Tuples - not regular ones.
breaking API when you move to a named type
I donāt think it would. Up-thread there has been a lot of discussion about an OneOf type. Or how multiple such types (OneOf2, OneOf3...) could be added. I think the āmultiple such typesā post will give you an ideas of how we could migrate to a variadic OneOf. To sum it up, we would just deprecate Either and encourage the use of OneOf in its place. So if youāre concerned that Either will limit our future options, you shouldnāt.
Either would not have the option to provide semantic naming to the individual values of a particular usage.
Compiler magic to convert the expression:
typealias MyOneOf
= (foo: Foo | bar: Bar)
Could be converted to:
typealias MyOneOf(first|second|)
= OneOf<Foo, Bar>
That would be similar to another post that proposes baking the closure labels into the identifier. That though is a future proposal altogether, that requires to be built one top of OneOf. There has also been talk of a ānativeā Union Tuple type, that will have custom compiler support. Nonetheless, that type is not acceptable to the Core Team, as they are hardly keen on the idea of an OneOf a type that is currently unimplementable as it requires advanced variadic genetics - correct me if Iāve misinterpreted. All in all, I would appreciate ideas that are applicable to the proposal of adding an Either like type. Lastly, Iād like to say that I donāt see a Union Type built into the compiler as a reasonable option, because it would further complicate the compiler and because there has been talk of even replacing Tuples with a normal type.
Iām assuming you are talking about Union Tuples - not regular ones.
I donāt believe union tuple is a common term.
Iām talking about the comparison between tuples and structs, vs either/anyof and enum.
The tuple (Double, Double) isnāt nearly as useful as a labeled (x:,y:), (r:, angle:), (width:, height:), etc. Either<String, Int> fails to allow for capturing what the semantics actually are - a string or int of what? When is the value one vs the other?
A tuple has clear limitations where it does not support encapsulation or polymorphism - you cannot have a tuple implement a protocol or add additional methods. You can do both of these with an Either type. This creates tension in that people can leverage an either/anyof type long after they should have moved to enum (perhaps because moving from either to enum is a breaking change). Examples of changes might be adding properties to give āfriendly namesā to left/right, implementing protocols like Comparable, etc.
I think @Dmitriy_Ignatyev lays out pretty compelling reasons for Either
's addition:
Great ideaš. Having Either<First, Second> Type in Standard Library allows us to use the same type across libraries. Similar situation was with the Result<Success, Failure> Type.
Either type is very useful with UDF / Rx / Combine and other cases. Some examples:
let completion: (Result<Either<AuthAgreement, ActiveLogin>, Error>) -> Void func eitherRequest<L, R>(_ target: Target, completion: @escaping NetworkCompletion<Either<L, R>>) let quizScoreViewModel: Either<ScoreViewModel, String> let quizAnswer: Either<String, [QuizAnswerVariant]> let showError: Either<ErrorVM, TextSheetVM>
I feel like a need to clarify that Either
won't try to replace all 2-case enums and neither will OneOf
replace all enums, just as Tuples haven't replaced struct
s. Tuples still have labels and they are more concise, still though struct
s are usually preferred. Either
just as Tuples, fits naturally in places where the creation of a new type just doesn't make sense. For example, such a type is present in the Standard Library, in the "automatic protocol satisfaction" proposal, in function builders it would make sense if it was used instead of two separate - and confusing - buildEither
methods. Additionally, in places where the creation of a new type is currently not allowed:
struct FooGeneric {
func fooGeneric<Baz>(baz: Baz) {
enum State { case bar(Bar), case foo(Foo) } // ā
...
}
}
of course we can do:
struct FooGeneric {
enum State { case bar(Bar), case foo(Foo) }
func fooGeneric<Baz>(baz: Baz) {
...
}
}
but in the above cases if we use State just in fooGeneric it would make much more sense for an Either
type to take State
's place. Not to mention the boilerplate that would be required should we want Equatable
conformance.
I recognise that Either
is not the ideal type, we'd all like to have. Nonetheless, I feel that it is needed for the cases outlined above - or at least it would make them more pleasant to deal with. Finally, let's not forget that having an OneOf
as I mentioned before, is not an option currently, so adding Either
for now seems like a good option and discussing how to best implement is the goal of this discussion. If you have any other ideas that are realistic for the above examples and is more elegant than Either feel free to share them.
Finally, let's not forget that having an
OneOf
as I mentioned before, is not an option currently, so addingEither
for now seems like a good option and discussing how to best implement is the goal of this discussion.
One important thing to keep in mind is that there is really no adding something to the standard library "for now". If something is added to the standard library, then it will be there for a very long time unless the Swift team is willing to break source and/or binary-compatibility to remove it.
Thus, additions to the standard library should meet a very high bar, and any such proposal should explain why it is critical that it be added now instead of focusing those efforts on building up the lower-level foundations of the language like variadic generics and constructing some way of expressing variadic cases so that something better could be provided, sinceāby your own post aboveā"Either
is not the ideal type".
One important thing to keep in mind is that there is really no adding something to the standard library "for now". If something is added to the standard library, then it will be there for a very long time unless the Swift team is willing to break source and/or binary-compatibility to remove it.
This is why I am opposed to introducing Either
and why I think it is appropriate to discuss things like structural sum types in this thread.
instead of focusing those efforts on building up the lower-level foundations of the language like variadic generics and constructing some way of expressing variadic cases so that something better could be provided, sinceāby your own post aboveā"
Either
is not the ideal type".
First of all, Iād like to address the part regarding focusing our efforts on variadic generics. I think everyone wants variadic generics, especially me. Iāve been working with function builders and without variadic generics some parts are really mess. But I hate to see, that discussion about variadic generics has been going on for years, yet still to this date thereās no such feature. Not to mention that an OneOf type would require probably an advanced Variadics feature, as repetition of an element - OneOf<Foo, Foo> - should not be allowed.
What has been presented in this thread are real problems - in my previous post I think I mentioned some. So, seeing function builders, the standard library and other projects result to custom types and more repetitive code - that in most cases are just boilerplate - I though that a sensible direction would be a standardized Either type. And many counter-proposals I donāt see a viable alternative. What Either offers is a great set of features - custom conformance, a standardized type etc.
Yes I still think Either isnāt ideal overall, but itās best for now, where there is a need for such a type. Furthermore, if a new Variadic type comes along and renders the use of a two-case Either type useless, it could slowly be deprecated and removed.
To sum up, there are problems that require an Either like type, whether itāll be named Either or OneOf doesnāt matter, what matters is that those problems be solved. Once an OneOf comes along, which will most likely take a while, Either will have already helped in a lot of situations and eliminated a lot of boilerplate.