Swift の Optional パターンを駆使する

こんにちは。2023年7月からはてなマンガアプリチームで働いています、id:fxwx23 です。

はてなに入社する前は React Native (TypeScript) や Go などを書くことが多かったため、 Swift を書くことは2021年の夏以来となります(!)。Swift を書くことを本格的に再開できることになったので、早速自分の復習も兼ねて Patterns の話を少しまとめておこうと思います。

Swift の Patterns は、 値を一致させて分解する単一の値または複合値の構造を表します。 Patterns には以下の種類があります。

今回は、 自分としても発見があった Optional Pattern に焦点をあてていきたいと思います。

Optional Pattern とは

Optional Pattern は以下のように説明されています。

An optional pattern matches values wrapped in a some(Wrapped) case of an Optional enumeration. Optional patterns consist of an identifier pattern followed immediately by a question mark

Optional Pattern は Identifier Pattern の直後に ? を置くことで表現され、 Optional<Wrapped>some(Wrapped) ケースにラップされた値と一致させることができます。(自分はSwiftは2系から書いていましたが、このパターンは知らずに使っていました...🙈)

let someOptional: Int? = 23
if case let x? = someOptional {
    print(x)
}

また、Optional Pattern は Optional の Enumeration Case Pattern のシンタックスシュガーと記載されており、

optional patterns are syntactic sugar for Optional enumeration case patterns

先ほど挙げたコード例は Enumeration Case Pattern と同等と解釈することができます。

let someOptional: Int? = 23
if case .some(let x) = someOptional {
    print(x)
}

上記の例だと、単純なので if let x = someOptional { ... } と書くことが多いと思います。今なら SE-0345: if let shorthand を使うでしょう。

if let someOption { ... }

では、Optional Pattern の使い所はどこにあるでしょうか。

Optional Pattern の使い所

まず、自分が Optional Pattern を調べるきっかけになった Associated Values を持つ Enumeration Case Pattern です。Optional Pattern を使う前は Associated Values が Optional なら、取り出した上で if let で unwrap するというようなコードを書いていましたが、

enum SomeEnum {
    case left(Int?)
}
            
let foo = SomeEnum.left(23)
if case .left(let x) = foo, let x {
    print(x)
    // 23
}

Optional Pattern は Associated Values にも有効なので、このように書くことができます。

let foo = SomeEnum.left(23)
if case .left(let x?) = foo {
    print(x)
    // 23
}

// これはつまり以下と同等
if case .left(.some(let x)) = foo {
    print(x)
}

実はこれは Patterns の説明の冒頭で説明されています。switch 文の case ラベル、do 文の catch 句、または ifwhileguard 、または for-in 文の case 条件で使用することができることがわかります。

These include enumeration case patterns, optional patterns, expression patterns, and type-casting patterns. You use these patterns in a case label of a switch statement, a catch clause of a do statement, or in the case condition of an if, while, guard, or for-in statement.

もう一つ 2つの Optional の文字列の順序が必要なシーンを考えてみます。意見が分かれるところですが、両方の文字列に値がある場合に使用される Optional Pattern は、読みやすい選択肢ではないかと感じました。

func compare(_ lhs: String?, _ rhs: String?) -> ComparisonResult {
    switch (lhs, rhs) {
    case (.none, .none): return .orderedSame
    case (.some, .none): return .orderedDescending
    case (.none, .some): return .orderedAscending
    // 以下は case (.some(let lhs), .some(let rhs)): と同等
    case (let lhs?, let rhs?):
        if lhs == rhs {
            return .orderedSame
        }
        return lhs < rhs ? .orderedAscending : .orderedDescending
    }
 }

最後に

あらためて Swift の強力な表現力を知ることができました。 Optional をサポートする他の言語ではどうなっているか気になってきましたね(?)。

Swift をこれから学ぶ方、普段から書いている人も是非 The Swift Programming Language をもう一度読んでみることをお勧めします。自分も読み直す中で面白い発見があればまた記事にしていきたいと思います。


(実は初ブログなのです)