Skip to content

Instantly share code, notes, and snippets.

@omochi
Created May 24, 2019 10:22
Show Gist options
  • Save omochi/0039ceac42f7da84caf9ff6d5d92a278 to your computer and use it in GitHub Desktop.
Save omochi/0039ceac42f7da84caf9ff6d5d92a278 to your computer and use it in GitHub Desktop.

slidenumber: true autoscale: true

Opaque Result Typeの解説

omochimetaru

わいわいswiftc #11


5秒なら


これを見て

Proposal SE-0244 Opaque Result Type

https://github.com/apple/swift-evolution/blob/master/proposals/0244-opaque-result-types.md


これを見て

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

https://qiita.com/koher/items/338d2f2d0c4731e3508f


解説するなら


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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment