Opaque Types¶ 不透明型¶
A function or method with an opaque return type hides its return value’s type information. Instead of providing a concrete type as the function’s return type, the return value is described in terms of the protocols it supports. Hiding type information is useful at boundaries between a module and code that calls into the module, because the underlying type of the return value can remain private. Unlike returning a value whose type is a protocol type, opaque types preserve type identity—the compiler has access to the type information, but clients of the module don’t. 不透明戻り型をもつ関数またはメソッドは、それの戻り値のもつ型情報を隠します。ある具象型を関数のもつ戻り型として提供する代わりに、戻り値はそれがサポートするプロトコルそれらの観点から記述されます。型情報を隠すことは、あるモジュールとモジュールへと呼び出しをするコードとの間の境界で役立ちます、なぜなら戻り値のその基礎をなす型は、プライベートのままであることが可能だからです。それの型があるプロトコル型である値を返すのとは違い、不透明型は型同一性を保全します — コンパイラは型情報にアクセスします、しかしモジュールのクライアントはしません。
The Problem That Opaque Types Solve¶ 不透明型が解決する問題¶
For example, suppose you’re writing a module that draws ASCII art shapes. The basic characteristic of an ASCII art shape is a draw()
function that returns the string representation of that shape, which you can use as the requirement for the Shape
protocol:
例えば、あなたがさまざまなASCIIアート形状を描くモジュールを記述していると仮定してください。あるASCIIアート形状の基本的な特徴は、その形状の文字列表現を返すdraw()
関数です、それはあなたが要件としてShape
プロトコルに対して使用できます:
- protocol Shape {
- func draw() -> String
- }
- struct Triangle: Shape {
- var size: Int
- func draw() -> String {
- var result: [String] = []
- for length in 1...size {
- result.append(String(repeating: "*", count: length))
- }
- return result.joined(separator: "\n")
- }
- }
- let smallTriangle = Triangle(size: 3)
- print(smallTriangle.draw())
- // *
- // **
- // ***
You could use generics to implement operations like flipping a shape vertically, as shown in the code below. However, there’s an important limitation to this approach: The flipped result exposes the exact generic types that were used to create it. あなたは総称体を使って、ある形状を垂直に裏返すような演算を実装できました、下のコードで示されるように。しかしながら、重大な限界がこの取り組みにはあります:その裏返された結果は、それを作成するのに使われた正確な総称体型を露出します。
- struct FlippedShape<T: Shape>: Shape {
- var shape: T
- func draw() -> String {
- let lines = shape.draw().split(separator: "\n")
- return lines.reversed().joined(separator: "\n")
- }
- }
- let flippedTriangle = FlippedShape(shape: smallTriangle)
- print(flippedTriangle.draw())
- // ***
- // **
- // *
This approach to defining a JoinedShape<T: Shape, U: Shape>
structure that joins two shapes together vertically, like the code below shows, results in types like JoinedShape<FlippedShape<Triangle>, Triangle>
from joining a flipped triangle with another triangle.
2つの形状を一緒に垂直につなぐJoinedShape<T: Shape, U: Shape>
構造体を定義するこの取り組みは、下のコードが示すように、裏返した三角を別の三角とつなぐことからJoinedShape<FlippedShape<Triangle>, Triangle>
のような型という結果になります。
- struct JoinedShape<T: Shape, U: Shape>: Shape {
- var top: T
- var bottom: U
- func draw() -> String {
- return top.draw() + "\n" + bottom.draw()
- }
- }
- let joinedTriangles = JoinedShape(top: smallTriangle, bottom: flippedTriangle)
- print(joinedTriangles.draw())
- // *
- // **
- // ***
- // ***
- // **
- // *
Exposing detailed information about the creation of a shape allows types that aren’t meant to be part of the ASCII art module’s public interface to leak out because of the need to state the full return type. The code inside the module could build up the same shape in a variety of ways, and other code outside the module that uses the shape shouldn’t have to account for the implementation details about the list of transformations. Wrapper types like JoinedShape
and FlippedShape
don’t matter to the module’s users, and they shouldn’t be visible. The module’s public interface consists of operations like joining and flipping a shape, and those operations return another Shape
value.
ある形状の作成についての詳細な情報を露出することは、ASCIIアートモジュールのもつパブリックインターフェイスの一部であることを意図する型それらに漏れ出ていくのを許します、完全な戻り型を公表する必要があるのが原因で。モジュール内のコードは、同じ形状をさまざまな方法で作り上げることができました、そしてその形状を使うモジュール外のその他のコードは、ずらっとある変形それらについての実装詳細に関知する必要はないはずです。JoinedShape
とFlippedShape
のようなラッパー型は、モジュールのユーザにとって重要ではありません、そしてそれらは可視でないべきです。モジュールのもつパブリックインターフェイスは、形の結合および裏返しのような演算からなります、そしてそれらの演算は別のShape
値を返します。
Returning an Opaque Type¶ 不透明型を返す¶
You can think of an opaque type like being the reverse of a generic type. Generic types let the code that calls a function pick the type for that function’s parameters and return value in a way that’s abstracted away from the function implementation. For example, the function in the following code returns a type that depends on its caller: あなたは、不透明型を総称体の逆であるように考えることができます。総称体型は、ある関数を呼び出すコードに、その関数のもつパラメータと戻り値に対する型を、関数実装から離れて抽象化されるある方法で選択させます。例えば、以下のコードの関数は、それの呼び出し側に依存する、ある型を返します。
- func max<T>(_ x: T, _ y: T) -> T where T: Comparable { ... }
The code that calls max(_:_:)
chooses the values for x
and y
, and the type of those values determines the concrete type of T
. The calling code can use any type that conforms to the Comparable
protocol. The code inside the function is written in a general way so it can handle whatever type the caller provides. The implementation of max(_:_:)
uses only functionality that all Comparable
types share.
max(_:_:)
を呼び出すコードは、x
とy
に対して値を選びます、そしてそれらの値の型はT
の具象型を決定します。呼び出しているコードは、Comparable
プロトコルに準拠するあらゆる型を使用できます。関数の内側のコードは、ある一般化された方法で記述されます、それでそれは呼び出し側が提供するどんな型でも取り扱うことができます。max(_:_:)
の実装は、全てのComparable
型が共有する機能性だけを使います。
Those roles are reversed for a function with an opaque return type. An opaque type lets the function implementation pick the type for the value it returns in a way that’s abstracted away from the code that calls the function. For example, the function in the following example returns a trapezoid without exposing the underlying type of that shape. それらの役割は、不透明戻り型をもつある関数に対しては逆にされます。ある不透明型は、関数実装に、それが返す値に対する型を、その関数を呼び出すコードから離れて抽象化されるある方法で選択させます。例えば、以下の例での関数は、ある台形を、その形状の基礎をなす型を露出することなしに返します。
- struct Square: Shape {
- var size: Int
- func draw() -> String {
- let line = String(repeating: "*", count: size)
- let result = Array<String>(repeating: line, count: size)
- return result.joined(separator: "\n")
- }
- }
- func makeTrapezoid() -> some Shape {
- let top = Triangle(size: 2)
- let middle = Square(size: 2)
- let bottom = FlippedShape(shape: top)
- let trapezoid = JoinedShape(
- top: top,
- bottom: JoinedShape(top: middle, bottom: bottom)
- )
- return trapezoid
- }
- let trapezoid = makeTrapezoid()
- print(trapezoid.draw())
- // *
- // **
- // **
- // **
- // **
- // *
The makeTrapezoid()
function in this example declares its return type as some Shape
; as a result, the function returns a value of some given type that conforms to the Shape
protocol, without specifying any particular concrete type. Writing makeTrapezoid()
this way lets it express the fundamental aspect of its public interface—the value it returns is a shape—without making the specific types that the shape is made from a part of its public interface. This implementation uses two triangles and a square, but the function could be rewritten to draw a trapezoid in a variety of other ways without changing its return type.
この例でのmakeTrapezoid()
関数は、それの戻り型をsome Shape
として宣言します;その結果、関数は、Shape
プロトコルに準拠するある与えられた型の値を返します、何らかの特定の具象型を指定することなしに。makeTrapezoid()
をこの方法で書くことは、それにそれのパブリックインターフェイスの基本となる面 — それが返す値はある形状である — を表現させます、特定の型を作ることなしにです、それはその形状がそれのパブリックインターフェイスの一部に由来して作られることです。この実装は、2つの三角形とある四角形を使います、しかし関数は、台形をさまざまな他の方法で描画するよう書き直されることがそれの戻り型を変更することなしにできました。
This example highlights the way that an opaque return type is like the reverse of a generic type. The code inside makeTrapezoid()
can return any type it needs to, as long as that type conforms to the Shape
protocol, like the calling code does for a generic function. The code that calls the function needs to be written in a general way, like the implementation of a generic function, so that it can work with any Shape
value that’s returned by makeTrapezoid()
.
この例は、不透明型が総称体型のまるで逆であるやり方に光を当てます。makeTrapezoid()
内部のコードは、それが必要とするどんな型でも返すことが、その型がShape
プロトコルに準拠する限りは可能です、呼び出しているコードが総称体関数に対してするように。関数を呼び出すコードは、ある一般的な方法で書かれることを必要とします、総称体関数の実装のように、それでそれは、makeTrapezoid()
によって返されるあらゆるShape
と仕事できます。
You can also combine opaque return types with generics. The functions in the following code both return a value of some type that conforms to the Shape
protocol.
あなたはまた、不透明戻り型を総称体と結合できます。以下のコードでの関数は両方とも、Shape
プロトコルに準拠する何らかの型の値を返します。
- func flip<T: Shape>(_ shape: T) -> some Shape {
- return FlippedShape(shape: shape)
- }
- func join<T: Shape, U: Shape>(_ top: T, _ bottom: U) -> some Shape {
- JoinedShape(top: top, bottom: bottom)
- }
- let opaqueJoinedTriangles = join(smallTriangle, flip(smallTriangle))
- print(opaqueJoinedTriangles.draw())
- // *
- // **
- // ***
- // ***
- // **
- // *
The value of opaqueJoinedTriangles
in this example is the same as joinedTriangles
in the generics example in the The Problem That Opaque Types Solve section earlier in this chapter. However, unlike the value in that example, flip(_:)
and join(_:_:)
wrap the underlying types that the generic shape operations return in an opaque return type, which prevents those types from being visible. Both functions are generic because the types they rely on are generic, and the type parameters to the function pass along the type information needed by FlippedShape
and JoinedShape
.
この例でのopaqueJoinedTriangles
の値は、この章の前の不透明型が解決する問題節の総称体の例でのjoinedTriangles
と同じです。しかしながら、その例での値と違い、flip(_:)
とjoin(_:_:)
は基礎をなす型それらをラップします、それらは総称体の形状演算が不透明戻り型において返すものです、それはこれらの型が可視であるのを防ぎます。両方の関数は総称体です、なぜならそれらが頼りにする型が総称体であるからです、そして関数への型パラメータは、FlippedShape
とJoinedShape
によって必要とされる型情報を伝達します。
If a function with an opaque return type returns from multiple places, all of the possible return values must have the same type. For a generic function, that return type can use the function’s generic type parameters, but it must still be a single type. For example, here’s an invalid version of the shape-flipping function that includes a special case for squares: 不透明戻り型をもつ関数が複数の場所から返るならば、可能な戻り値の全ては、同じ型を持たなければなりません。総称体関数に対して、その戻り型は、関数のもつ総称体型パラメータを使用できます、しかしそれは依然として単一の型でなければなりません。例えば、ここに形状反転関数の無効なバージョンがあります、それは正方形に対する特別な場合を含みます:
- func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
- if shape is Square {
- return shape // Error: return types don't match(エラー:戻り型は一致しません)
- }
- return FlippedShape(shape: shape) // Error: return types don't match(エラー:戻り型は一致しません)
- }
If you call this function with a Square
, it returns a Square
; otherwise, it returns a FlippedShape
. This violates the requirement to return values of only one type and makes invalidFlip(_:)
invalid code. One way to fix invalidFlip(_:)
is to move the special case for squares into the implementation of FlippedShape
, which lets this function always return a FlippedShape
value:
あなたがこの関数をSquare
で呼び出すならば、それはSquare
を返します;そうでなければ、それはFlippedShape
を返します。これは、ただ1つの型だけの値を返すという要件に違反します、そしてinvalidFlip(_:)
を無効なコードにします。invalidFlip(_:)
を修正する1つの方法は、正方形の特別な事例をFlippedShape
の実装へと移動することです、それはこの関数に常にFlippedShape
値を返させます:
- struct FlippedShape<T: Shape>: Shape {
- var shape: T
- func draw() -> String {
- if shape is Square {
- return shape.draw()
- }
- let lines = shape.draw().split(separator: "\n")
- return lines.reversed().joined(separator: "\n")
- }
- }
The requirement to always return a single type doesn’t prevent you from using generics in an opaque return type. Here’s an example of a function that incorporates its type parameter into the underlying type of the value it returns: 常に単一の型を返すという要件は、あなたが総称体を不透明戻り型において使う妨げになりません。ここに、ある関数の例があります、それはそれの型パラメータを、それが返す値のその基礎をなす型へと組み入れます。
- func `repeat`<T: Shape>(shape: T, count: Int) -> some Collection {
- return Array<T>(repeating: shape, count: count)
- }
In this case, the underlying type of the return value varies depending on T
: Whatever shape is passed it, repeat(shape:count:)
creates and returns an array of that shape. Nevertheless, the return value always has the same underlying type of [T]
, so it follows the requirement that functions with opaque return types must return values of only a single type.
この場合には、戻り値のその基礎をなす型はT
に依存して変動します:どんな形状がそれに渡されても、repeat(shape:count:)
はその形状からなるある配列を作成して返します。それにもかかわらず、戻り値は常に同じ基礎をなす型の[T]
を持ちます、それでそれは、不透明戻り型をもつ関数はもっぱら単一の型の値だけを返さなければならないという要件に従います。
Differences Between Opaque Types and Protocol Types¶ 不透明型とプロトコル型の間の違い¶
Returning an opaque type looks very similar to using a protocol type as the return type of a function, but these two kinds of return type differ in whether they preserve type identity. An opaque type refers to one specific type, although the caller of the function isn’t able to see which type; a protocol type can refer to any type that conforms to the protocol. Generally speaking, protocol types give you more flexibility about the underlying types of the values they store, and opaque types let you make stronger guarantees about those underlying types. 不透明型を返すことは、プロトコル型を関数の戻り型として使うことと大変よく似て見えます、しかしこれら2つの種類の戻り型は、それらが型同一性を保全するかどうかにおいて異なります。不透明型は1つの特定の型を参照します、とはいえ関数の呼び出し側はどの型か知ることができません;プロトコル型はそのプロトコルに準拠するどんな型でも参照できます。一般的に言えば、プロトコル型はそれらが格納する値の基礎をなす型についてあなたにより柔軟性を与えます、そして不透明型はそれらの基礎をなす型についてあなたにより確固とした保証をさせます。
For example, here’s a version of flip(_:)
that uses a protocol type as its return type instead of an opaque return type:
例えば、ここにflip(_:)
のあるバージョンがあります、それはあるプロトコル型をそれの戻り型として使います、不透明戻り型の代わりに:
- func protoFlip<T: Shape>(_ shape: T) -> Shape {
- return FlippedShape(shape: shape)
- }
This version of protoFlip(_:)
has the same body as flip(_:)
, and it always returns a value of the same type. Unlike flip(_:)
, the value that protoFlip(_:)
returns isn’t required to always have the same type—it just has to conform to the Shape
protocol. Put another way, protoFlip(_:)
makes a much looser API contract with its caller than flip(_:)
makes. It reserves the flexibility to return values of multiple types:
このバージョンのprotoFlip(_:)
は、flip(_:)
と同じ本文を持ちます、そしてそれは常に同じ型の値を返します。flip(_:)
とは違い、protoFlip(_:)
が返す値は、常に同じ型を持つように要求されません — それは単にShape
プロトコルに準拠しなければならないだけです。言い換えれば、protoFlip(_:)
は、それの呼び出し側とずっとゆるいAPI契約をします、flip(_:)
がするよりも。それは複数の型の値を返す柔軟性を確保します:
- func protoFlip<T: Shape>(_ shape: T) -> Shape {
- if shape is Square {
- return shape
- }
- return FlippedShape(shape: shape)
- }
The revised version of the code returns an instance of Square
or an instance of FlippedShape
, depending on what shape is passed in. Two flipped shapes returned by this function might have completely different types. Other valid versions of this function could return values of different types when flipping multiple instances of the same shape. The less specific return type information from protoFlip(_:)
means that many operations that depend on type information aren’t available on the returned value. For example, it’s not possible to write an ==
operator comparing results returned by this function.
この改訂版のコードは、Square
のインスタンスまたはFlippedShape
のインスタンスを返します、どの形状が渡されるかに依存して。この関数によって返される2つの裏返された形状が、完全に異なる型を持つかもしれません。この関数の他の有効なバージョンそれらは、異なる型の値を返すことが、同じ形状の複数のインスタンスを裏返す時にありえます。protoFlip(_:)
からの具体性のより少ない戻り型情報は、型情報に依存する多くの演算が、その返された値の上で利用可能でないことを意味します。例えば、この関数によって返される結果を比較する==
演算子を書くことは可能ではありません。
- let protoFlippedTriangle = protoFlip(smallTriangle)
- let sameThing = protoFlip(smallTriangle)
- protoFlippedTriangle == sameThing // Error(エラー)
The error on the last line of the example occurs for several reasons. The immediate issue is that the Shape
doesn’t include an ==
operator as part of its protocol requirements. If you try adding one, the next issue you’ll encounter is that the ==
operator needs to know the types of its left-hand and right-hand arguments. This sort of operator usually takes arguments of type Self
, matching whatever concrete type adopts the protocol, but adding a Self
requirement to the protocol doesn’t allow for the type erasure that happens when you use the protocol as a type.
この例の最後の行でのエラーは、いくつかの理由で発生します。当面の問題は、Shape
がそれのプロトコル要件として==
演算子を含まないことです。あなたがそれを加えることを試みるならば、あなたがぶつかる次の問題は、==
演算子はそれの左手および右手の引数の型を知る必要があることです。この種の演算子は通常は、どんな具象型がそのプロトコルを採用しても合う、型Self
の引数を取ります、しかしSelf
要件をプロトコルに加えることは型消去を想定しません、それはあなたがプロトコルをひとつの型として使う場合に予期せず起こります。
Using a protocol type as the return type for a function gives you the flexibility to return any type that conforms to the protocol. However, the cost of that flexibility is that some operations aren’t possible on the returned values. The example shows how the ==
operator isn’t available—it depends on specific type information that isn’t preserved by using a protocol type.
プロトコル型を戻り型として関数に使うことは、あなたに、そのプロトコルに準拠するどんな型でも返す柔軟性を与えます。しかしながら、その柔軟性の対価は、いくつかの演算がその返される値の上で可能でないということです。この例は、どのように==
演算子が利用可能でないか示します — それは具体的な型情報に依存します、それはプロトコル型を使うことでは保全されないものです。
Another problem with this approach is that the shape transformations don’t nest. The result of flipping a triangle is a value of type Shape
, and the protoFlip(_:)
function takes an argument of some type that conforms to the Shape
protocol. However, a value of a protocol type doesn’t conform to that protocol; the value returned by protoFlip(_:)
doesn’t conform to Shape
. This means code like protoFlip(protoFlip(smallTriange))
that applies multiple transformations is invalid because the flipped shape isn’t a valid argument to protoFlip(_:)
.
この取り組みの別の問題は、形状変換が入れ子にされないことです。三角形を裏返すことの結果は、型Shape
の値です、そしてprotoFlip(_:)
関数はShape
プロトコルに準拠する型の引数を取ります。しかしながら、あるプロトコル型からなるある値は、そのプロトコルに準拠しません;protoFlip(_:)
によって返される値は、Shape
に準拠しません。これは、複数の変換を適用するprotoFlip(protoFlip(smallTriange))
のようなコードが無効であることを意味します、なぜなら裏返された形状はprotoFlip(_:)
への有効な引数ではないからです。
In contrast, opaque types preserve the identity of the underlying type. Swift can infer associated types, which lets you use an opaque return value in places where a protocol type can’t be used as a return value. For example, here’s a version of the Container
protocol from Generics:
対照的に、不透明型は基礎をなす型の同一性を保全します。スウィフトは関連型を推論できます、それはあなたに不透明戻り値を、プロトコル型が戻り値として使われることができないところあちこちで使用させます。例えば、ここに総称体からの、Container
プロトコルのあるバージョンがあります:
- protocol Container {
- associatedtype Item
- var count: Int { get }
- subscript(i: Int) -> Item { get }
- }
- extension Array: Container { }
You can’t use Container
as the return type of a function because that protocol has an associated type. You also can’t use it as constraint in a generic return type because there isn’t enough information outside the function body to infer what the generic type needs to be.
あなたはContainer
を関数の戻り型として使用できません、なぜならそのプロトコルが関連型を持つからです。あなたはまたそれを制約として総称体戻り型において使用できません、なぜならどうあることをその総称体型が必要とするか推論するのに十分な情報が関数本文の外側にないからです。
- // Error: Protocol with associated types can't be used as a return type.(Error: 関連型を持つプロトコルは戻り型として使われることができません。)
- func makeProtocolContainer<T>(item: T) -> Container {
- return [item]
- }
- // Error: Not enough information to infer C.(Error: Cを推論するための情報が十分でない。)
- func makeProtocolContainer<T, C: Container>(item: T) -> C {
- return [item]
- }
Using the opaque type some Container
as a return type expresses the desired API contract—the function returns a container, but declines to specify the container’s type:
不透明型some Container
を戻り型として使うことは、望まれるAPI契約を表現します — 関数はあるコンテナ(容れ物)を返します、しかしコンテナのもつ型を指定することを辞退します。
- func makeOpaqueContainer<T>(item: T) -> some Container {
- return [item]
- }
- let opaqueContainer = makeOpaqueContainer(item: 12)
- let twelve = opaqueContainer[0]
- print(type(of: twelve))
- // Prints "Int"(「Int」を出力します)
The type of twelve
is inferred to be Int
, which illustrates the fact that type inference works with opaque types. In the implementation of makeOpaqueContainer(item:)
, the underlying type of the opaque container is [T]
. In this case, T
is Int
, so the return value is an array of integers and the Item
associated type is inferred to be Int
. The subscript on Container
returns Item
, which means that the type of twelve
is also inferred to be Int
.
twelve
の型は、Int
であると推論されます、それは型推論が不透明型を扱うという事実を例示します。makeOpaqueContainer(item:)
の実装において、不透明コンテナの基礎をなす型は、[T]
です。この場合に、T
はInt
です、それで戻り値は整数からなるある配列です、そしてItem
関連型はInt
と推論されます。Container
上での添え字は、 Item
を返します、それはtwelve
の型もまたInt
に推論されることを意味します。