LoginSignup
38
20

More than 3 years have passed since last update.

Opaque Result Typeの解説

Posted at
1 / 43

この記事はこちらの転載です


Opaque Result Typeの解説

omochimetaru

わいわいswiftc #11


5秒なら


これを見て

Proposal SE-0244 Opaque Result Type


これを見て

Swift 5.1 に導入される Opaque Result Type とは何か


解説するなら


Opaque Result Type

パフォーマンスを保ちつつ、
真の型を隠蔽する新機能


動機


真の型を隠蔽

「あるプロトコルを返す」と言いたいことがある

func loadUsers() -> ??? // Sequence of User

具体的な型を返してしまうと将来変更できない

// ver 1.0.0
func loadUsers() -> [User]

// ver 1.1.0
func loadUsers() -> LazySequence<User>

従来の方法

Type ErasureやExistentialで返す

func loadUsers() -> AnySequence<User>
  • 型の正しさを損なう
  • パフォーマンスを損なう

型の正しさを損なう

let a = AnyCollection<String>(["hello"])
let b = AnyCollection<Character>("hello")
let c = b[a.startIndex]

実行時(!)エラー

Collection.Indexに関する型情報が失われている


書き込み

protocol P {}
extension Int : P {}

struct Container {
    var value: P {
        get { return _value }
        set { _value = newValue as! Int }
    }
    var _value: Int = 1
}

実質的にsuper typeなので、contravarianceポジションで壊れる


パフォーマンスを損なう

ラッパーに包まれてる分遅い


解決方法


Opaque Result Type

some P

「プロトコルPを満たす何らかの型」を表す記法


関数の返り値で使える

extension Int : P {}

func getP() -> some P {
    return 3
}

プロパティで使える

var propertyP: some P {
    get { ... }
    set { ... }
}

associated typeがついてても使える

func numbers() -> some MutableCollection {
    return [1, 2, 3]
}
var xs = numbers()
let i0 = xs.startIndex
let i1 = xs.index(after: i0)
xs[i0] = xs[i1]
print(xs) // [2, 2, 3]

ここがミソ

2つのsome Pは違う

func f1() -> some P
func f2() -> some P

var a = f1()
let b = f2()
a = b // コンパイルエラー

ORTは関数単位のアイデンティティを持つ

下記のような状態を想像すると良い

struct ResultOfF1 : P {}
func f1() -> ResultOfF1

struct ResultOfF2 : P {}
func f2() -> ResultOfF2

var a = f1()
let b = f2()
a = b // コンパイルエラー

ORTの抽象化表現

some Pとして意味付けされるため、将来のバージョンで真の型を変更できる

ソース互換性だけではなく、バイナリ互換性があり、再コンパイルせずに変更できる

// ver 1.0.0
func f() -> some P { return 3 }

// ver 1.1.0
func f() -> some P { return "3" }

問題の解決をみていく

  • 型情報の保持
  • 書き込み
  • パフォーマンス

型情報の保持

func numbers() -> some Collection { return [1, 2, 3] }
func message() -> some Collection { return "hello" }

let a = numbers()
let b = message()
let x = a[b.startIndex] // 型エラー

書き込み可能

struct S {
    var value: some P {
        get { return _value }
        set { _value = newValue as! Int }
    }
    var _value: Int = 3
}

var s = S()
let x = s.value
s.value = x         // OK
s.value = Int(1)    // Error
s.value = "hello"   // Error

パフォーマンス

コンパイラが真の型を知っている時は、最適化により真の型として扱うコードに特殊化されるため、不要なパフォーマンスロスが無い


「最適化できるときは速い」という意味ではExistentialと同じ。何が違うのか?

Existentialはsuper typeであるため、「他の型が入っている可能性がない」証明が必要。

ORTは「知らないある型」なので、その証明は不要。単にその真の型を知れば良いだけ。

結果として同一モジュールなら常に最適化可能。


発展


ORTの導入にあたって議論が拡大していった。
収拾をつけるため新しいマニフェストが作られた。

Improving the UI of generics
https://forums.swift.org/t/improving-the-ui-of-generics/22814

Generic Manifestoに続く指針


ORTはジェネリクスと似ている

// Pである真の型を返す
func makeSomeP() -> some P

// Pである真の型Xを受け取る
func useGeneP<X: P>(_ x: X)

これとは違う

// Pである真の型Xを返す
func makeGeneP<X: P>() -> X

このXは呼び出し側が決める

some Pは呼び出され側が決める


Reverse Generics

呼び出され側が決めるジェネリックパラメータという新概念を考えてみる

func makeRevGeneP() -> <X: P> X

ORTはReverse Genericのsugarとみなせる

// Pである真の型を返す
func makeSomeP() -> some P

// Pである真の型Xを返す
func makeRevGeneP() -> <X: P> X

Reverse Genericsを使うと、表現力が上がる

// 2つのsome Pの関連を書けない
func makePS() -> (some P, some P)

// 2つの「同一の」Pを満たす型
func makePS() -> <X: P> (X, X)

ORT構文の拡張案

func makePS() 
    -> (some P A, some P B)
    where A == B

Opaque Result Type → Reverse Generics

?? → Generics


Opaque Argument Type

もしsomeを引数部分に書けたら、Genericsのsugarとみなせる

// Pである真の型を受け取る
func useSomeP(_ x: some P)

// Pである真の型Xを受け取る
func useGeneP<X: P>(_ x: X)

// Pである真の型を受け取る
func useSomeP(_ x: some P)

// Pである真の型Xを受け取る
func useGeneP<X: P>(_ x: X)

頭の中で一度Xを記憶しないので読みやすい、流行りそう


ORTとExistential

よく似ている

// Pを満たす**とある**型を返す
func makeSomeP() -> some P

// Pを満たす**どんな**型でも代入できる
var b: P = ...

Existentialのany記法

// Pを満たす**とある**型を返す
func makeSomeP() -> some P

// Pを満たす**どんな**型でも代入できる
var b: any P

対称的できれい


Protocol制約とExistentialの類似

初級者殺し

// Pは制約
func useGeneP<X: P>(_ x: X)

// PはExistential
func useAnyP(_ x: P)

違う機能は違う記法のほうがわかりやすい

// Pは制約
func useGeneP<X: P>(_ x: X)

// PはExistential
func useAnyP(_ x: any P)
38
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
20