☕ Swift の some(Opaque Result Type)を生活にたとえて理解する



1. APIとは?

  • API = 外から見える「メニュー表」や「窓口」
  • 「ホットコーヒーをください」と注文できるのは、カフェがそういう窓口を公開しているから。
  • どう作っているか(豆の種類・器具)は本来隠すべき「内部実装」。

👉 「APIで内部実装を過剰に公開」とは、
「メニュー表に『ネスカフェ赤瓶をスプーン2杯』と書いてしまうこと」。
→ 後から豆を変えたらメニューと実態がズレて APIが壊れる


2. Tupleとは?

  • Tuple = 小さな袋にまとめたデータ
  • 例: ("太郎", 25) = 名前と年齢が1つの袋に。
  • 軽量なデータのまとめ方。

3. なぜ some が必要なのか?

(A) 型を具体的に返す場合

func makeAnimal() -> Cat { Cat() }

👉 これは「この関数は絶対にネコを返します」と内部実装まで公開している状態。
→ もし将来 Dog に変えたいと思ったら API を変えざるを得ない。柔軟性ゼロ。


(B) プロトコル型(Existential, any)を返す場合

func makeAnimal() -> any Animal { Cat() }

👉 これなら「Animal を返す」と言えるので実装は隠せる。
ただし Swift では Existential Container という箱に詰め込む仕組みが働く。

  • Cat は本来1バイトで済む
  • Dog は8バイト必要
  • でも any Animal という箱に入れると両方に対応するため 40バイト確保される

つまり

  • 値を渡すとき → 箱に詰める処理(オーバーヘッド)
  • メソッドを呼ぶとき → 箱を開いて間接呼び出し(オーバーヘッド)

👉 柔軟だが遅い


(C) some を返す場合(Opaque Result Type)

func makeAnimal() -> some Animal { Cat() }
  • 「Animal を返す」としか外には見えない
  • でも内部的には Cat に固定されているので 最適化できて速い
  • 内部を隠しつつ、-> Cat と同等のパフォーマンス

👉 柔軟かつ速い


4. ジェネリクスと「リバースジェネリクス」

ジェネリクス(普通の <T>)

func useAnimal<A: Animal>(_ animal: A) {
    animal.foo()
}
  • 呼び出し側が「A = Cat」と決める。
  • 利用者が型を選ぶ仕組み。

リバースジェネリクス(逆の発想)

func makeAnimal() -> <A: Animal> A {
    return Cat()
}
  • 実装者が「A = Cat」と決めて、利用者には「Animal として扱えるよ」とだけ見せる。
  • これを簡単に書けるシンタックスシュガーが -> some Animal

👉 「some = リバースジェネリクスの糖衣構文」というのはこのこと。


5. SwiftUI と some View の関係

var body: some View {
    VStack {
        Text("Hello")
        Button("Tap") { }
    }
}
  • 本当の型は VStack<TupleView<(Text, Button<Text>)>> のように爆長い。
  • それを API として公開すると 内部構造をバラすことになるし、実装を少し変えただけで壊れる。
  • some View とすることで「View を返す」だけを約束できる。

👉 内部を隠して安全に保ちつつ、速い


6. まとめ(全部の比較)

  • -> Cat
    👉 速いけど中身を公開しすぎ(柔軟性なし)
  • -> any Animal
    👉 柔軟だが遅い(Existential Containerのオーバーヘッド)
  • -> some Animal
    👉 内部は隠しつつ速い(リバースジェネリクスの糖衣構文)

📝 結論

  • API = メニュー表
  • Tuple = 小さな袋
  • 内部実装の過剰公開 = メニューに「豆の種類」まで書くこと
  • some = 実装者が型を決めて隠す(柔軟 + 高速)
  • any = 利用者がどの型でも扱える(柔軟だけど遅い)
  • SwiftUI の some View は、この「隠すけど速い」メリットを最大限に使っている。

コメント

タイトルとURLをコピーしました