Suppose that Either
is declared in the following way,
enum Either<T, U> {
case Left(T)
case Right(U)
}
I want language-level support for Either
in a consistent way with for Optional
.
Int?
is a syntactic sugar of Optional<Int>
.
// Optional
let o1: Optional<Int> = .Some(42)
let o2: Int? = .Some(42)
It seems good that String|Int
is a syntactic sugar of Either<String, Int>
.
// Either
let e1: Either<String, Int> = .Right(42)
let e2: String|Int = .Right(42)
Foo|Bar
is a familiar notation because it is used for Union Types in some languages. (e.g. TypeScript, Ceylon and Flow)
// Optional
let o1: Int? = .Some(42)
let o2: Int? = 42
// Either
let e1: String|Int = .Right(42)
let e2: String|Int = 42
Int
is a subtype of Optional<Int>
in Swift.
// Optional
class A {
func foo() -> Int? {
return nil
}
}
class B: A {
// changes the return type to Int
override func foo() -> Int {
return 42
}
}
Int
may be a subtype of Either<String, Int>
.
// Either
class A {
func foo() -> String|Int {
return "error
}
}
class B: A {
// changes the return type to Int
override func foo() -> Int {
return 42
}
}
Then Either
works like Union Types even though it is an Enumeration. It is consistent with Optional
.
// Optional
let foo: Int? = 42
let bar: Double? = foo?.bar
// Either
let foo: String|Int = 42
let bar: String|Double = foo?.bar
// Optional
let foo: Int? = 42
let bar: Double = foo!.bar
// Either
let foo: String|Int = 42
let bar: Double = foo!.bar
// Optional
let fooOrNil: Int? = 42
if let foo: Int = fooOrNil {
...
}
// Either
let fooOrError: String|Int = 42
if let foo: Int = fooOrError {
...
}
// Optional
let foo: Int? = try? foo("42")
// Either
let foo: Error|Int = try| foo("42")
// Optional
class Foo {
let x: Int
init?(x: String) {
do {
x = try foo(x)
} catch {
return nil
}
}
}
let foo: Foo? = Foo(x: 42)
// Either
class Foo {
init|(x: String) {
do {
x = try foo(x)
} catch let error as Error {
return .Left(error)
}
}
}
let foo: Error|Foo = Foo(x: 42)
Although it does not need language-level support for ??
because it is just a function.
// Optional
let foo: Int? = nil
let foo2: Int = foo ?? 42
// Either
let foo: String|Int = "error"
let bar: Int = foo ?? 42
Also it does not need language-level support for map
and flatMap
. But I must refer to them because they are necessary.
// Optional
let o1: Int? = 42
let o2: Int? = nil
let square: Int? = o1.map { $0 * $0 }
let mean: Int? = o1.flatMap { x in o2.flatMap { y in (x + y) / 2 } }
// Either
let e1: String|Int = 42
let e2: String|Int = "error"
let square: String|Int = e1.map { $0 * $0 }
let mean: String|Int = e1.flatMap { x in e2.flatMap { y in (x + y) / 2 } }
Foo|Bar|Baz
can be interpreted as both Either<Either<Foo, Bar>, Baz>
and Either<Foo, Either<Bar, Baz>>
. I think the former one is better if we mainly use Either
for error handlings. Because I want Int
as a result of foo!
in the following code.
let fooOrError: ErrorA|ErrorB|Int = ...
let foo: Int = fooOrError!
We also need some notations like Foo|(Bar|Baz)
in order to specify Either<Foo, Either<Bar, Baz>>
.