Skip to content

Boxプロトコル型(Boxed Protocol Type)の説明を追加、TypesとSummary of the Grammarの表示方法の修正 #426

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
* [拡張\(Extensions\)](language-guide/extensions.md)
* [プロトコル\(Protocols\)](language-guide/protocols.md)
* [ジェネリクス\(Generics\)](language-guide/generics.md)
* [Opaque \(Opaque Types\)](language-guide/opaque-types.md)
* [Opaque 型とBox プロトコル型\(Opaque Types and Boxed Types\)](language-guide/opaque-types.md)
* [自動参照カウント ARC\(Automatic Reference Counting\)](language-guide/automatic-reference-counting.md)
* [メモリ安全性\(Memory Safety\)](language-guide/memory-safety.md)
* [アクセスコントロール\(Access Control\)](language-guide/access-control.md)
Expand Down
61 changes: 53 additions & 8 deletions language-guide/opaque-types.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# Opaque 型\(Opaque Types\)
# Opaque 型とBox 型\(Opaque Types and Boxed Types\)

最終更新日: 2022/12/3
最終更新日: 2023/5/28
原文: https://docs.swift.org/swift-book/LanguageGuide/OpaqueTypes.html

値の型に関する実装の詳細を隠す。

戻り値に不透明な型\(以下_Opaque 型_\)を持つ関数またはメソッドは、その戻り値の型情報を隠します。関数の戻り値の型として具体的な型を提供する代わりに、準拠するプロトコルの観点から戻り値を記述します。型情報を隠すことで、戻り値の基になる型を private のままにすることができるため、モジュールとモジュールを呼び出すコードの間に境界を設けることに役立ちます。プロトコル型の値を返すのとは異なり、Opaque 型は具体的な型情報を保持し続けます。コンパイラは型情報にアクセスできますが、モジュールのクライアントはアクセスできません。
Swift は値の型情報を隠すために 2 つの方法を提供します。不透明な型\(以下_Opaque 型_\)と Box 型です。
型情報を隠すことで、戻り値の基になる型を private のままにすることができるため、モジュールとモジュールを呼び出すコードの間に境界を設けることに役立ちます。戻り値に Opaque 型を持つ関数またはメソッドは、その戻り値の型情報を隠します。関数の戻り値の型として具体的な型を提供する代わりに、準拠するプロトコルの観点から戻り値を記述します。
コンパイラは型情報にアクセスできますが、モジュールのクライアントはアクセスできません。
Box プロトコル型は、任意の型のインスタンスを格納することができます。Box プロトコル型は、与えられたプロトコルに準拠する任意の型のインスタンスを格納することができます。
ボックス型プロトコル型は型の同一性を保持しません。つまり、値の特定の型は実行時までわからず、また、異なる値が保存されるため、時間の経過とともに変化する可能性があります。

## <a id="the-problem-that-opaque-types-solve">Opaque 型が解決する問題\(The Problem That Opaque Types Solve\)</a>

Expand Down Expand Up @@ -176,11 +180,52 @@ func `repeat`<T: Shape>(shape: T, count: Int) -> some Collection {

この場合、戻り値の基になる型は `T` によって異なります。どの形状が渡されても、`repeat(shape:count:)` はその形状の配列を作成して返します。それにもかかわらず、戻り値は常に同じ `[T]` の基になる型のため、Opaque な戻り値の型を持つ関数は単一の型の値のみを返す必要があるという要件に従います。

## Box プロトコル型\(Boxed Protocol Types\)

Box プロトコル型は存在型とも呼ばれることがありますが、これは「`T` がプロトコルに準拠するような型 `T` が存在する」という表現に由来しています。Box プロトコル型を作るには、プロトコルの名前の前に `any` と書きます。以下はその例です:

```swift
struct VerticalShapes: Shape {
var shapes: [any Shape]
func draw() -> String {
return shapes.map { $0.draw() }.joined(separator: "\n\n")
}
}

let largeTriangle = Triangle(size: 5)
let largeSquare = Square(size: 5)
let vertical = VerticalShapes(shapes: [largeTriangle, largeSquare])
print(vertical.draw())
```

上の例では、`VerticalShapes` は、`Shape` のタイプを `[any Shape]`、つまり Box Shape 型要素の配列、と宣言しています。配列の各要素には、異なる型を入れることができ、それらの型の各々は、`Shape` プロトコルに準拠する必要があります。この実行時の柔軟性をサポートするために、Swift は必要なときに間接層を追加します。つまり、この間接的な存在は*ボックス*と呼ばれ、パフォーマンスコストがかかります。

`VerticalShapes` 型の中で、コードは `Shape` プロトコルで必要とされるメソッド、プロパティ、およびサブスクリプトを使用することができます。例えば、`VerticalShapes` の `draw()` メソッドは、配列の各要素の `draw()` メソッドを呼び出します。`Shape` が `draw()` メソッドを要件にしているため、このメソッドを利用できます。これに対して、三角形の `size` プロパティや、`Shape` が要件していないプロパティやメソッドにアクセスしようとすると、エラーが発生します。

`Shape` に使用できる 3 つのタイプを対比してみましょう:

- `struct VerticalShapes<S: Shape>` と `var shapes: [S]` を書くことで、ジェネリクスを使い、ある特定の形状を要素とする配列を作り、その配列とやり取りをするすべてのコードから、その特定の型が識別できるようにする
- Opaque 型を使用する場合、`var shapes:[some Shape]` と書くと、特定の形状を要素とする配列を作成し、その特定の型の識別は隠されます
- Box プロトコル型を使って、`var shapes:[any Shape]` と書くと、異なる形状の要素を格納できる配列ができ、それらの型の識別は隠される

この場合、Box プロトコル型は、`VerticalShapes` の呼び出し元が異なる種類の形状を一緒に混ぜることができる唯一のアプローチです。

Box 値の基礎となる型がわかっている場合は、`as` キャストを使用することができます。例えば、以下のような感じです:

```swift
if let downcastTriangle = vertical.shapes[0] as? Triangle {
print(downcastTriangle.size)
}
// "5"
```

より詳細は[Downcasting\(ダウンキャスト\)](../language-guide/type-casting.md#downcasting)を参照ください。

## Opaque 型とプロトコルの違い\(Differences Between Opaque Types and Protocol Types\)

Opaque 型を返すことは、プロトコル型を関数の戻り値の型として使用する場合と非常によく似ていますが、これら 2 種類の戻り値の型は、型情報を保持するかどうかが異なります。Opaque 型は 1 つの特定の型を参照しますが、関数の呼び出し側はどの型を参照するかはわかりません。プロトコル型は、プロトコルに準拠する任意の型を参照できます。一般に、プロトコル型では、格納する値の基になる型についてより柔軟に対応でき、Opaque 型を使用すると、基になる型についてより強力な保証を行うことができます。
Opaque 型を返すことは、Box プロトコル型を関数の戻り値の型として使用する場合と非常によく似ていますが、これら 2 種類の戻り値の型は、型情報を保持するかどうかが異なります。Opaque 型は 1 つの特定の型を参照しますが、関数の呼び出し側はどの型を参照するかはわかりません。Box プロトコル型は、プロトコルに準拠する任意の型を参照できます。一般に、Box プロトコル型では、格納する値の基になる型についてより柔軟に対応でき、Opaque 型を使用すると、基になる型についてより強力な保証を行うことができます。

例えば、Opaque 型の代わりにプロトコル型を戻り値の型として使用する、`flip(_:)` のバージョンを次に示します:
例えば、Opaque 型の代わりに Box プロトコル型を戻り値の型として使用する、`flip(_:)` のバージョンを次に示します:

```swift
func protoFlip<T: Shape>(_ shape: T) -> Shape {
Expand Down Expand Up @@ -210,11 +255,11 @@ protoFlippedTriangle == sameThing // エラー

この例の最終行のエラーは、いくつかの理由で発生します。当面の問題は、`Shape` のプロトコル要件に `==` 演算子が含まれていないことです。追加しようとすると、次に遭遇する問題は、`==` 演算子がその左辺と右辺の引数の型を知る必要があることです。この種の演算子は通常、`Self` 型の引数を取り、プロトコルに準拠する具体的な型に一致しますが、プロトコルに `Self` 要件を追加すると、プロトコルを型として使用することができなくなります。

プロトコル型を関数の戻り値の型として使用すると、プロトコルに準拠する任意の型を柔軟に返すことができます。ただし、その柔軟性によって、返された値の一部の操作が犠牲になります。この例では、プロトコル型では保持されない特定の型情報に依存しているため、`==` 演算子が使用できないことを示しています。
Box プロトコル型を関数の戻り値の型として使用すると、プロトコルに準拠する任意の型を柔軟に返すことができます。ただし、その柔軟性によって、返された値の一部の操作が犠牲になります。この例では、Box プロトコル型では保持されない特定の型情報に依存しているため、`==` 演算子が使用できないことを示しています。

このアプローチのもう 1 つの問題は、形状変換をネストできないことです。三角形を反転した結果は、`Shape` 型の値で、`protoFlip(_:)` 関数は、`Shape` プロトコルに準拠した何らかの型の引数を取ります。ただし、プロトコル型はそのプロトコル自体に準拠しません。`protoFlip(_:)` によって返される値は `Shape` に準拠していません。これは、反転した形状が `protoFlip(_:)` の有効な引数ではないため、複数の変換を適用する `protoFlip(protoFlip(smallTriangle))` のようなコードが無効だということを意味します。
このアプローチのもう 1 つの問題は、形状変換をネストできないことです。三角形を反転した結果は、`Shape` 型の値で、`protoFlip(_:)` 関数は、`Shape` プロトコルに準拠した何らかの型の引数を取ります。ただし、Box プロトコル型はそのプロトコル自体に準拠しません。`protoFlip(_:)` によって返される値は `Shape` に準拠していません。これは、反転した形状が `protoFlip(_:)` の有効な引数ではないため、複数の変換を適用する `protoFlip(protoFlip(smallTriangle))` のようなコードが無効だということを意味します。

対照的に、Opaque 型は、基になる型情報を保持します。Swift は関連型を推論できるため、プロトコル型を戻り値として使用できない場所で Opaque 型な戻り値の型を使用できます。例えば、これは [Generics\(ジェネリクス\)](generics.md)の `Container` プロトコルの別のバージョンです:
対照的に、Opaque 型は、基になる型情報を保持します。Swift は関連型を推論できるため、Box プロトコル型を戻り値として使用できない場所で Opaque 型な戻り値の型を使用できます。例えば、これは [Generics\(ジェネリクス\)](generics.md)の `Container` プロトコルの別のバージョンです:

```swift
protocol Container {
Expand Down
55 changes: 11 additions & 44 deletions language-guide/protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
}
```

> NOTE:
> プロトコルは型のため、
> Swiftの他の型に一致するように(`Int`、`String` や `Double` など)
> 名前は大文字から始めてください。(`FullyNamed` や `RandomNumberGenerator` など)

## <a id="property-requirements">プロパティ要件\(Property Requirements\)</a>

プロトコルは、特定の名前と型のインスタンスプロパティまたは型プロパティを要件にできます。プロトコルでは、格納プロパティか計算プロパティかを指定しません。必要なプロパティの名前と型を指定するだけです。また、プロトコルは、各プロパティが get のみか、get/set どちらも必要かどうかも指定します。
Expand Down Expand Up @@ -245,54 +250,16 @@ class SomeSubClass: SomeSuperClass, SomeProtocol {

## <a id="protocols-as-types">型としてのプロトコル\(Protocols as Types\)</a>

プロトコルは、実際には機能を実装しません。それにもかかわらず、コード内で完全な型としてプロトコルを使用できます。プロトコルを型として使用することは、_存在型_と呼ばれることもあります。これは、「T がプロトコルに準拠するような型 T が存在する」というフレーズから来ています。

次のような他の型が許可されている多くの場所でプロトコルを使用できます。

* 関数、メソッド、またはイニシャライザのパラメータの型または戻り値の型として
* 定数、変数、またはプロパティの型として
* 配列、辞書、またはその他のコンテナ内のアイテムの型として

> NOTE
> プロトコルは型であるため、他の型 \(`Int`、`String`、`Double` など\) と同じように名前を大文字で始めます。\(`FullyNamed` や `RandomNumberGenerator` など\)

型として使用されるプロトコルの例を次に示します:

```swift
class Dice {
let sides: Int
let generator: RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
}
```

この例では、ボードゲームで使用する n 面のサイコロを表す `Dice` という新しいクラスを定義しています。`Dice` のインスタンスには、側面の数を表す `sides` と呼ばれる整数のプロパティと、ダイスの出目を作成するための乱数ジェネレータを提供する `generator` と呼ばれるプロパティがあります。

`generator` プロパティの型は `RandomNumberGenerator` です。したがって、`RandomNumberGenerator` プロトコルに準拠する任意の型のインスタンスを設定できます。このプロパティに割り当てるインスタンスには、インスタンスが `RandomNumberGenerator` プロトコルに準拠する必要があることを除いて、他に何も必要ありません。型は `RandomNumberGenerator` なので、`Dice` クラス内のコードは、このプロトコルに準拠する `generator` のみを使用できます。つまり、その具体的なジェネレータの型で定義されているメソッドやプロパティを使用することはできません。ただし、[Downcasting\(ダウンキャスト\)](../language-guide/type-casting.md#downcasting)で説明されているように、スーパークラスからサブクラスにダウンキャストできるのと同じ方法で、プロトコル型から準拠した具体的な型にダウンキャストできます。
プロトコルは、実際には機能を実装しません。
それにもかかわらず、コード内で型としてプロトコルを使用できます。

`Dice` には、初期状態を設定するためのイニシャライザもあります。このイニシャライザには、`RandomNumberGenerator` 型の `generator` と呼ばれるパラメータがあります。新しい `Dice` インスタンスを初期化するときに、このパラメータに `RandomNumberGenerator` に準拠する型の値を渡すことができます
プロトコルを型として使用する最も一般的な方法は、プロトコルをジェネリック制約として使用することです。ジェネリック制約を持つコードは、プロトコルに準拠する任意の型を扱うことができ、特定の型は API を使用する側のコードで選択されます。例えば、引数を取る関数を呼び出したとき、その引数の型がジェネリックであれば、呼び出し元がその型を選びます

`Dice` は、1 からサイコロの面の数までの整数値を返す 1 つのインスタンスメソッド、`roll` を提供しています。このメソッドは、ジェネレータの `random()` メソッドを呼び出して `0.0` から `1.0` の間の新しい乱数を作成し、この乱数を使用して正しい範囲内でサイコロの目を作成します。`generator` は `RandomNumberGenerator` に準拠することがわかっているため、呼び出すべき `random()` メソッドが存在していることは保証されています
Opaque 型を持つコードは、プロトコルに準拠した何らかの型を使って機能します。基本的な型はコンパイル時に判明し、API 実装はその型を選択しますが、その型の正体は API のクライアントから隠されています。例えば、ある関数の戻り値の型を隠し、その値があるプロトコルに準拠していることだけを保証します

`Dice` クラスを使用して、`LinearCongruentialGenerator` インスタンスを乱数ジェネレータとして使用して、6 面体のサイコロを作成する方法を次に示します:
Box プロトコル型を持つコードは、プロトコルに準拠する、実行時に選択された任意の型で動作します。この実行時の柔軟性をサポートするために、Swift は必要なときに間接的なレイヤーを追加します。これは、*ボックス*として知られており、これはパフォーマンスコストを伴います。この柔軟性が理由で、Swift はコンパイル時に基礎となる型を知らなりません。つまり、プロトコルによって必要とされるメンバのみにアクセスできることを意味します。基礎となる型で定義された他の API にアクセスするには、実行時にキャストが必要です。

```swift
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
print("Random dice roll is \(d6.roll())")
}
// Random dice roll is 3
// Random dice roll is 5
// Random dice roll is 4
// Random dice roll is 5
// Random dice roll is 4
```
プロトコルをジェネリック制約として使用することについては、[ジェネリクス\(Generics\)](../language-guide/generics.md)を参照してください。Opaque 型と Box プロトコル型については、[Opaque 型とBox プロトコル型\(Opaque Types and Boxed Types\)](language-guide/opaque-types.md)を参照してください。

## <a id="delegation">委譲\(Delegation\)</a>

Expand Down
Loading