Skip to content

Instantly share code, notes, and snippets.

@koher
Created September 7, 2015 15:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save koher/abc04f7a03f9eb717787 to your computer and use it in GitHub Desktop.
Save koher/abc04f7a03f9eb717787 to your computer and use it in GitHub Desktop.

I want Swift's Either like ...

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.

Foo|Bar

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)

Implicit Conversions

// Optional
let o1: Int? = .Some(42)
let o2: Int? = 42
// Either
let e1: String|Int = .Right(42)
let e2: String|Int = 42

Subtyping

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.

Chaining

// Optional
let foo: Int? = 42
let bar: Double? = foo?.bar
// Either
let foo: String|Int = 42
let bar: String|Double = foo?.bar

Forced Unwrapping

// Optional
let foo: Int? = 42
let bar: Double = foo!.bar
// Either
let foo: String|Int = 42
let bar: Double = foo!.bar

Binding

// Optional
let fooOrNil: Int? = 42
if let foo: Int = fooOrNil {
    ...
}
// Either
let fooOrError: String|Int = 42
if let foo: Int = fooOrError {
    ...
}

try|

// Optional
let foo: Int? = try? foo("42")
// Either
let foo: Error|Int = try| foo("42")

init| (Failable Initializers)

// 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

map and flatMap

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

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>>.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment