SwiftのOptionalはただのenum?

  • 63
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

SwiftのOptional Typeについて、よく次のような説明を見かけます。

T?Optional<T> のシンタックスシュガーです。

実際に Optionalenum で、次のように定義されています。

Optionalの定義
enum Optional<T> {
    case None
    case Some(T)
}

これだけ見ると、まるで Optional がSwiftの型システム上に実装されているように見えます。一方でSwiftを書いていると、 Optional はSwiftの型システムの一部であるように感じられます。

さて、 enum とジェネリクスを使えばSwiftの型システム上に Optional を実装できるのでしょうか

Optional と等価な enum を自作することはできるか、実際に試してみましょう。

なお、これは Swift 1.1 時点での話です。将来的に言語仕様や型システムが変更される可能性があります。

検証

次のような自作 Optional を作って実験します。

(A)
enum MyOptional<T> {
    case None
    case Some(T)
}

実験1: if文

次のコードは、Optional Typeを使ったありきたりなコードです。

(B)
let stringOrNil: String? = "ABC"
if let string = stringOrNil {
    println(string)
}

これをシンタックスシュガーなしで書くと次のようになります。

(C)
let stringOrNil: Optional<String> = Optional.Some("ABC")
if let string = stringOrNil {
    println(string)
}

MyOptional で書くと次の通りです。

(D)
let stringOrNil: MyOptional<String> = MyOptional.Some("ABC")
switch stringOrNil {
case .Some(let string):
    println(string)
default:
    break
}

本当はif文を使って次のように書ければよかったのですが、そのような構文はありません。

(E)
// ※このコードは正しいSwiftのコードではありません。
let stringOrNil: MyOptional<String> = MyOptional.Some("ABC")
if stringOrNil == MyOptional.Some(let string) {
    println(string)
}

しかし、 if let ... が使えないからと言って、 Optional と等価な型を実装できないということにはなりません。例えば、 if let ...switch のシンタックスシュガーと考えることができます(実際にシンタックスシュガーになっているかは別として、そのように考えても辻褄が合います)。

そう考えれば、 (B)(C) は次のコードと等価となります。これは (D) とそっくりです。

(F)
let stringOrNil: Optional<String> = Optional.Some("ABC")
switch stringOrNil {
case .Some(let string):
    println(string)
default:
    break
}

実験2: Optional Chaining

Optional Chaningはいかにも Optional のための特別な構文のように思えます。

(G)
let baz = foo?.bar?.baz

しかし、このコードは OptionalflatMap メソッドを使って次のように書き換えることができます(ただし、 Swift 標準では flatMap がないので swiftf のような Extension が必要です。 flatMap についての詳細は "SwiftのOptionalを極める" をご覧下さい)。

(H)
let baz = foo.flatMap { $0.bar }.flatMap { $0.baz }

MyOptional にも flatMap メソッドを実装してみましょう(後で使うので併せて map メソッドも実装しておきます)。

(I)
enum MyOptional<T> {
    case None
    case Some(T)

    func flatMap<U>(f: (T) -> MyOptional<U>) -> MyOptional<U> {
        switch self {
        case .None:
            return .None
        case .Some(let t):
            return f(t)
        }
    }

    func map<U>(f: (T) -> U) -> MyOptional<U> {
        switch self {
        case .None:
            return .None
        case .Some(let t):
            return .Some(f(t))
        }
    }
}

すると、 MyOptional でもまったく同じコードが書けます。

Optional Chainingは flatMap メソッドの呼び出しのシンタックスシュガーと考えれば特に問題なさそうです。

実験3: 代入とCovariance

次のように継承関係にある二つのクラスがあるとします。

(J)
class Animal {}
class Cat: Animal {}

このとき、次のコードには一見何の問題もないように思えます。

(K)
let cat: Cat? = Cat()
let animal: Animal? = cat // Animal?にCat?を代入

しかし、前述の通り Animal?Cat? はシンタックスシュガーなので、本来は次のようなコードを意味します。

(L)
let cat: Optional<Cat> = Optional.Some(Cat())
let animal: Optional<Animal> = cat // Optional<Animal>にOptional<Cat>を代入

この代入が許されるには、 Optional<Cat>Optional<Animal> の派生型である必要があります。つまり、 Optional は(型パラメータ T について) Covariant でなければなりません。

実際に上記のコードを実行してみると問題なく動作します。どうやら Optional はCovariantなように見えます。

では、 MyOptional で同じことをするとどうなるでしょうか。

(M)
let cat: MyOptional<Cat> = MyOptional.Some(Cat())
let animal: MyOptional<Animal> = cat // コンパイルエラー!

なんとコンパイルエラーになってしまいました。どうやら自作のジェネリックな enum は(少なくともデフォルトでは)Covariantではないようです。

しかし、SwiftにはC#やScalaなどのように、型パラメータにVarianceを制御する構文がありません。

(N)
// ※このコードは正しいSwiftのコードではありません。
enum MyOptional<out T> { // Covariantにするために型パラメータにoutをつけたい
    case None
    case Some(T)
}

MyOptional をCovariantにする手段がないということは、やはり Optional はSwiftの型システム上に実装できない特別な型なのでしょうか。

しかし、次のように考えることもできます。

Covariantに見えた代入文はシンタックスシュガーで、実は (K)(L) は次のコードと等価なのではないでしょうか。

(O)
let cat: Optional<Cat> = Optional.Some(Cat())
let animal: Optional<Animal> = cat.map {$0}

そうであれば、 MyOptional でも同じように書くことができます。

(P)
let cat: MyOptional<Cat> = MyOptional.Some(Cat())
let animal: MyOptional<Animal> = cat.map {$0}

これも Optional が特別であることの証拠とは言えなさそうです。

実験4: オーバーライドとCovariance

代入以外に Optional がCovariantであるか確認する方法はないでしょうか。

派生型でメソッドをオーバーライドする場合、次のように戻り値の型を狭めることができます(Covariant return type)。

(Q)
class A {
    func animal() -> Animal {
        return Animal()
    }
}

class B : A {
    override func animal() -> Cat { // 戻り値の型をAnimalからCatに狭める
        return Cat()
    }
}

CatAnimal の派生型であり Animal として振る舞えるため、このように戻り値の型をより限定しても問題は起こりません。

では、次のコードはどうでしょうか。

(R)
class A {
    func animal() -> Animal? {
        return Animal()
    }
}

class B : A {
    override func animal() -> Cat? { // 戻り値の型をAnimal?からCat?に狭める
        return Cat()
    }
}

一見問題がなさそうですが、 Animal?Cat? はシンタックスシュガーなため、上記のコードは次のコードと等価です。

(S)
class A {
    func animal() -> Optional<Animal> {
        return Optional.Some(Animal())
    }
}

class B : A {
    override func animal() -> Optional<Cat> { // Optional<Animal>をOptional<Cat>に狭められる?
        return Optional.Some(Cat())
    }
}

このコードが許されるには、 Optional<Cat>Optional<Animal> の派生型である、つまり Optional が型パラメータ T についてCovariantである必要があります。

この方法であれば二つのメソッドの戻り値の型が直接比較されるため、代入時のようにシンタックスシュガーで回避することはできません。

上記のコードは問題なくコンパイルを通ります。どうやら Optional は本当にCovariantなようです

MyOptional がCovariantでないことは代入の実験で確認できていますが、念のためこの方法でも検証してみましょう。

(T)
class A {
    func animal() -> MyOptional<Animal> {
        return MyOptional.Some(Animal())
    }
}

class B : A {
    override func animal() -> MyOptional<Cat> { // コンパイルエラー!
        return MyOptional.Some(Cat())
    }
}

やはりコンパイルエラーになってしまいました。

これで、 Optional がSwiftの型システム上に実装されていると言うのは大分苦しくなってきました。しかし、好意的に考えれば次のような解釈は可能です。

Swiftの Optional は確かにCovariantですが、型パラメータにVarianceを制御する構文が まだ存在しない だけで、Variance自体はSwiftの型システムの一部なのかもしれません。

何か決定的な証拠はないでしょうか。

実験5: 派生型

次のコードを見て下さい。

(U)
class A {
    func animal() -> Animal? {
        return Animal()
    }
}

class B : A {
    override func animal() -> Animal { // 戻り値の型をAnimal?からAnimalに狭める
        return Animal()
    }
}

一見自然に見えますが、例によってシンタックスシュガーを剥がすと次のようになります。

(V)
class A {
    func animal() -> Optional<Animal> {
        return Optional.Some(Animal())
    }
}

class B : A {
    override func animal() -> Animal { // 戻り値の型をOptional<Animal>からAnimalに狭める?
        return Animal()
    }
}

これはさすがに厳しいように思います。 AnimalOptional<Animal> の派生型になりうるでしょうか。しかし、このコードはコンパイルを通ります。

念のため、 MyOptional でも試してみましょう。

(W)
class A {
    func animal() -> MyOptional<Animal> {
        return MyOptional.Some(Animal())
    }
}

class B : A {
    override func animal() -> Animal { // コンパイルエラー!
        return Animal()
    }
}

やはりコンパイルエラーになります。 enum がAssociated Valueを持っているときに、Associated Valueの型がその enum の派生型になる( AnimalMyOptional<Animal> の派生型になる)わけではないようです。

これは決定的な証拠ではないでしょうか。 Swiftの型システムは Optional を特別扱いし、 TOptional<T> の派生型として扱っている ように見えます。

しかし、Varianceのときと同じように、型システム上は AnimalMyOptional<Animal> の派生型になるかを制御でき、そのための構文がまだ存在しないだけと考えることはできないでしょうか。

VarianceについてはCovariantとContravariant、Invariantを使い分ける必要があるため、デフォルトはInvariantで、Varianceの制御のための構文が欠けていると考えても筋が通ります。しかし、 AnimalMyOptional<Animal> の派生型でいけない理由はないように思います。派生型になるかならないか制御する構文が欠けていると考えるのは不自然ではないでしょうか。やはり、現時点では Optional はSwiftの型システム上に実装されたものではなく、型システムの一部であると考えるのが自然だと思います。

結論

以上の実験より、現時点では Optional はSwiftの型システム上に実装されたものではなく、型システムの一部であると考えるのが自然だと思います(個人的な考察の結果なので、おかしな点があれば指摘してもらえるとうれしいです)。