Swift 4.1 のおける最も大きな変更の一つは Conditional Conformance 1のサポートです。 Conditional Conformance はジェネリクス関連の言語仕様の一つです。
本投稿では、 Conditional Conformance とは何か、何がうれしいのか、どのようなことができるようになったのかということを説明します。
Conditional Conformance とは
Swift 4.0 まででも、次のようなコードは正しく実行できました。
let a = [2, 3, 5]
let b = [2, 3, 5]
print(a == b) // true
しかし、 Swift 4.0 ではこちらのコードはコンパイルエラーになります。
// Swift 4.0
let a = [[2, 3], [5]]
let b = [[2, 3], [5]]
print(a == b) // コンパイルエラー
最初のコードでは a
と b
の型は [Int]
( Array<Int>
)ですが、その次のコードでは [[Int]]
( Array<Array<Int>>
)です。つまり、 Swift 4.0 までは [Int]
同士を ==
で比較することはできましたが、 [[Int]]
同士を ==
で比較することはできなかったということです。
Swift 4.1 からは [[Int]]
同士でも ==
で比較することができるようになりますが、これを実現しているのが Conditional Conformance です。
==
という関数
この挙動を理解するには、まず Swift の ==
が何なのかを理解する必要があります。 Swift では、 ==
を含め演算子の正体は関数です。たとえば、次のような関数を実装すると、自作の Foo
に対して ==
演算子が使えるようになります。
struct Foo { let value: Int }
func ==(lhs: Foo, rhs: Foo) -> Bool {
return lhs.value == rhs.value
}
自作した ==
はこのように使うことができます。
Foo(value: 42) == Foo(value: 42) // true
Foo(value: 42) == Foo(value: -1) // false
Equatable
とは
さて、 Swift にはこれとは別に Equatable
というプロトコルがあります。 Equatable
に適合する( conform する)型は ==
を使えると知っている人も多いんじゃないかと思います。 Int
や String
, Bool
などは ==
で比較することができますが、これらの型はすべて Equatable
に適合しています。
extension Int : Equatable {
static func ==(lhs: Int, rhs: Int) -> Bool {
...
}
}
しかし、さっき作った Foo
は Equatable
ではありませんでしたが、 ==
で比較することができました。一見、 Equatable
なんてなくても問題ないように思えます。 Equatable
だと何がうれしいのでしょうか?
たとえば、指定されたのと同じ値が Array
の中にいくつあるかを数える関数 countElements
を実装することを考えてみましょう。次のように使えるイメージです。
let a = [0, 1, 1, 2, 3, 5, 8]
countElements(in: a, equalTo: 1) // 2
この countElements
は次のように実装することができます。
func countElements(in array: [Int], equalTo value: Int) -> Int {
return array.reduce(0) { $0 + ($1 == value ? 1 : 0) }
}
しかし、これでは countElements
を [Int]
以外の型に使うことができません。
let b = ["s", "w", "i", "f", "t", "t", "w", "e", "e", "t", "s"]
countElements(in: b, equalTo: "s") // [Int] でないのでコンパイルエラー
もちろん、 [String]
を引数にとる countElements
を実装すればできるようになりますが、 [Double]
や [Bool]
などを考えるとキリがありません。
今、 array
の要素に対して行っている操作は ==
だけなので、 ==
を使える型を要素に持った Array
に対して一般的に countElements
を実装できれば問題は解決します。そして、 ==
で比較可能な型であるということを表すのが Equatable
プロトコルです。
Equatable
な要素を持つ Array
一般について countElements
を実装すると次のようになります。 T : Equatable
なので T
を ==
で比較できます。
func countElements<T : Equatable>(in array: [T], equalTo value: T) -> Int {
return array.reduce(0) { $0 + ($1 == value ? 1 : 0) }
}
この countElements
を使えば、先程はコンパイルエラーになった次のコードも正しく実行できます。
let b = ["s", "w", "i", "f", "t", "t", "w", "e", "e", "t", "s"]
countElements(in: b, equalTo: "s") // 2
しかし、自作した Foo
は Equatable
でないので、 [Foo]
に対しては countElements
を使うことができません。
let c = [Foo(value: 1), Foo(value: 1), Foo(value: 2), Foo(value: 3)]
countElements(in: c, equalTo: Foo(value: 1)) // コンパイルエラー
Foo
を Equatable
に適合させるには次のように実装します。
struct Foo { let value: Int }
extension Foo : Equatable {
static func ==(lhs: Foo, rhs: Foo) -> Bool {
return lhs.value == rhs.value
}
}
そうすれば Foo
も Equatable
なので、 [Foo]
に対しても countElements
を使えるようになります。
let c = [Foo(value: 1), Foo(value: 1), Foo(value: 2), Foo(value: 3)]
countElements(in: c, equalTo: Foo(value: 1)) // 2
これが Swift における ==
の仕組みと Equatable
の役割です。
[Int]
は Equatable
?
では冒頭の、 [Int]
は ==
で比較できるけど [[Int]]
は ==
で比較できないのはなぜか、という問に戻りましょう。そもそも、 Swift の Array
は Equatable
なのでしょうか?それとも Equatable
ではないのでしょうか?
たとえば、次のコードのように [Int]
同士は比較したいので Array
には Equatable
であってほしいように思います。
let a = [2, 3, 5]
let b = [2, 3, 5]
print(a == b) // true
しかし、 Equatable
でない要素を持つ Array
を考えることもできます。
そもそも、 Equatable
でない型とはどんな型でしょうか。たとえば、多くのエラー型では equality を定義するのが困難です。エラーが equal であるとはどういう状況でしょうか?発生した状況や行、スタックトレースが完全に等しければ、異なるタイミングで発生したエラーでも等しいのでしょうか?
クラスであればインスタンスが同一( ===
が true
)であることを equality の定義とすることもできるかもしれませんが、標準ライブラリの DecodingError
のように値型のエラーもあります。値型では ===
は使えません。無理やり DecodingError
を Equatable
に適合させようとして、保持する値がすべて等しかったときに等しいことにしたとしても、別のタイミングで発生したたまたま同じに見えるエラーを同値とすることに何か意味があるでしょうか?
このように、 equality を定義すること自体に意味が見出だせない型も存在します。そのような型では無理に ==
の意味を定義するより、比較しようとしたらコンパイルエラーになるのが自然でしょう。
let a: DecodingError = ...
let b: DecodingError = ...
print(a == b) // コンパイルエラー
Objective-C や Java などの昔ながらのオブジェクト指向言語では、 isEqual
や hash
が NSObject
のようなルートクラスに定義されており、必要に応じてこれをオーバーライドするようになっています。しかし、それでは本来 isEqual
や hash
を持つべきでない型もそれらを使えることになってしまいます。静的型付言語なのに isEqual
や hash
を持つべきかを型で表すことができず、適切にコンパイルエラーを出せないというのは敗北です。
そういう意味で、 Equatable
や Hashable
で明示的に ==
や hashValue
が定義されるべきかを表現することができる Swift は、 Objective-C 等の言語と比べてより成熟した静的型付言語だと言えるでしょう。
DecodingError
は Equatable
でないと言いました。そうであれば DecodingError
の Array
である [DecodingError]
も当然 Equatable
でないと考えるのが自然です。要素が等しいかどうかを判定できないのに、その Array
が等しいかどうかをどのように定義することができるでしょうか。
let a: [DecodingError] = ...
let b: [DecodingError] = ...
print(a == b) // コンパイルエラー
つまり、要素が Equatable
なら Array
も Equatable
に、要素が Equatable
でなければ Array
自体も Equatable
でなく なってほしいということです。
Conditional Conformance
要素が
Equatable
ならArray
もEquatable
に、要素がEquatable
でなければArray
自体もEquatable
でなく なってほしい
それをコードで表すと次のように書けます。 これが Conditional Conformance です。
extension Array : Equatable where Element : Equatable {
...
}
この例だと Element
が Equatable
かという条件( condition )によって Array
が Equatable
に適合( conform )するかを決定します。なので Conditional Conformance と呼びます。
しかし、 Swift 4.0 までは Conditional Conformance がサポートされていませんでした。だからといって、 [Int]
同士を ==
で比較できないというのはあまりにも不便すぎます。そのため、 Array
は Equatable
ではありませんが、次のような ==
関数を別途提供することで [Int]
同士の比較が実現されていました。
// Swift 4.0
func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool {
...
}
そういうわけで、 [Int]
は Equatable
ではないにも関わらず、これまでも ==
で比較することができていたのです。
// Swift 4.0
let a = [2, 3, 5]
let b = [2, 3, 5]
print(a == b) // true
しかし、 T : Equatable
に対して [T]
同士の ==
は標準ライブラリで提供されていますが、 [[T]]
の比較は提供されていません。それが、冒頭のコードがエラーになった理由です。
// Swift 4.0
let a = [[2, 3], [5]]
let b = [[2, 3], [5]]
print(a == b) // コンパイルエラー
Swift 4.1 からは Conditional Conformance によって [[Int]]
同士を ==
で比較できるようになりました。
// Swift 4.1
let a = [[2, 3], [5]]
let b = [[2, 3], [5]]
print(a == b) // true
[Int]
は Equatable
な要素 Int
を持つ Array
なので Equatable
です。同様に、 [[Int]]
は Equatable
な要素 [Int]
を持つので Equatable
です。
そのように再帰的に考えると、たとえ [[[[[[[[[[[[[[[[[Int]]]]]]]]]]]]]]]]]
であっても Equatable
になります。
// Swift 4.1
let a = [[[[[[[[[[[[[[[[[42]]]]]]]]]]]]]]]]]
let b = [[[[[[[[[[[[[[[[[42]]]]]]]]]]]]]]]]]
print(a == b) // true
これで、 Conditional Conformance のおかげで [[Int]]
等を ==
で比較できるようになったことがわかったと思います。しかし、それがそんなにうれしいことでしょうか?
Conditional Conformance が標準ライブラリにもたらす変化
ここからは、 Conditional Conformance が Swift の標準ライブラリにもたらす変化をいくつか紹介します。
Optional
まずは Array
と似たところで Optional
です。これまでの Optional
は Equatable
ではありませんでした。
たとえば、次のようなコードを書いた経験が一度はあると思いますが、 Int?
( Optional<Int>
)は Equatable
ではないので、本来はこの ==
は使えません。
let a: Int? = ...
if a == nil { ... }
Swift 4.0 までは Array
と同じように次のような ==
関数でお茶を濁していました。
// Swift 4.0
func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool {
...
}
Swift 4.1 からは Conditional Conformance で Int?
等も Equatable
になります。
// Swift 4.1
extension Optional : Equatable where Wrapped : Equatable {
static func ==(lhs: Wrapped?, rhs: Wrapped?) -> Bool {
...
}
}
これによって、 Int??
同士の比較をすることもできるようになりました。
// Swift 4.1
let a: Int?? = 42
let b: Int?? = 42
print(a == b) // true
他にも、 XCTAssertEqual
なんかにも影響があります。 XCTAssertEqual
は Equatable
な値を受け取ってチェックを行います。しかし、 Optional
は Equatable
でないのでそのままでは XCTAssertEqual
を使えません。そのため、 ==
同様 Optional
を受け取る特別な XCTAssertEqual
がオーバーロードされていました。
Swift 4.1 からは、 Optional<T> : Equatable where T : Equatable
なので、 XCTAssertEqual
一つで済むようになります。
let a: Int = 42
let b: Int? = 42
// ↓の二つは Swift 4.0 では別の関数、
// Swift 4.1 では同じ関数
XCTAssertEqual(a, a)
XCTAssertEqual(b, b)
Codable
これまでは Array
や Optional
のような具象型の、 Equatable
の適合について見てきました。今度はプロトコル視点で、 Codable
について見てみます。
Codable
がおもしろいのは、 Conditional Conformance がない Swift 4.0 の時点ですでに Array
や Optional
が Codable
になっていることです。
なので、 [Int]
や Int?
をエンコード/デコードすることができます。
// Swift 4.0
import Foundation
let a = [2, 3, 5]
try! JSONEncoder().encode(a) // OK
しかし、もし Codable
でない要素を持つ Array
をエンコード/デコードしようとするとどうなるのでしょうか?
// Swift 4.0
import Foundation
struct Foo {}
let a = [Foo(), Foo(), Foo()]
try! JSONEncoder().encode(a) // ???
Array
は Codable
なので当然コンパイルは通ります。
// Swift 4.0
import Foundation
struct Foo {}
let a = [Foo(), Foo(), Foo()]
try! JSONEncoder().encode(a) // コンパイル可
しかし、 Foo
を encode
することはできないので実行時エラーになります。これをコンパイルエラーにできないのは静的型付言語としてイケてないですね。
// Swift 4.0
import Foundation
struct Foo {}
let a = [Foo(), Foo(), Foo()]
try! JSONEncoder().encode(a) // 実行時エラー
Swift 4.1 では Conditional Conformance で、要素が Codable
なときだけ Array
も Codable
になります。
// Swift 4.1
extension Array : Codable where Element : Codable {
...
}
その結果として、先程のコードは見事にコンパイルエラーになります。
// Swift 4.1
import Foundation
struct Foo {}
let a = [Foo(), Foo(), Foo()]
try! JSONEncoder().encode(a) // コンパイルエラー
おもしろいのは、 Swift 4.0 で、 Equatable
は Equatable
でない 側に、 Codable
は Codable
である 側に倒されているということです。
==
については [T]
だけ関数版でサポートしておけばお茶を濁すことができましたが、 Codable
はそういうわけにはいきません。 JSON だと複雑なネスト構造を持ちますし、 Codable
は JSON に限った話でもありません。 ==
のときのように encode
をオーバーロードして済む話ではなく、ネストを扱うためには広く Codable
として受け入れて、要素が Codable
でない場合は実行時エラーで対処するしかなかったのでしょう。
Swift 4.1 からは Conditional Conformance のおかげでジェネリックな型を安全に Codable
にすることができるようになりました。
Range
Conditional Conformance がちょっと違った種類の影響を与えるものに Range
ファミリーがあります。これは、 Conditional Conformance 関連で僕が一番待ち望んでいたものでもあります。
Swift の Range
ファミリーはややこしいです。 Swift 4.0 ではざっと次のような型がありました。多すぎてぞっとしますね。
// Swift 4.0
let a: Range<Int> = 2..<5
let b: CountableRange<Int> = 2..<5
let c: ClosedRange<Int> = 2...5
let d: CountableClosedRange<Int> = 2...5
let e: PartialRangeFrom<Int> = 2...
let f: CountablePartialRangeFrom<Int> = 2...
let g: PartialRangeUpTo<Int> = ..<5
let h: PartialRangeThrough<Int> = ...5
let i: UnboundedRange = (...)
Conditional Conformance によってコメントアウトした型をなくすことができます。これで 1 / 3 の型を減らせます。
// Swift 4.1
let a: Range<Int> = 2..<5
// let b: CountableRange<Int> = 2..<5
let c: ClosedRange<Int> = 2...5
// let d: CountableClosedRange<Int> = 2...5
let e: PartialRangeFrom<Int> = 2...
// let f: CountablePartialRangeFrom<Int> = 2...
let g: PartialRangeUpTo<Int> = ..<5
let h: PartialRangeThrough<Int> = ...5
let i: UnboundedRange = (...)
よく見ると、なくすことができる型は右辺が他と重複しています。たとえば Range<Int>
と CountableRange<Int>
は両方とも右辺が 2..<5
です。では Range
と CountableRange
の違いは何でしょうか?
Range
は単に lowerBound
から upperBound
までの範囲を表すだけですが、 CountableRange
はその間の値を数え上げることができる型です。たとえば、 2..<5
という CountableRange
は 2, 3, 4
です。イテレートして for
文でループすることもできます。
// Swift 4.0
let a: CountableRange<Int> = 2..<5
for x in a {
print(x) // 2, 3, 4 が順に表示される
}
しかし、同じ 2..<5
でも Range
ではこのようなことはできません。 Range
はあくまで範囲を表すだけだからです。
// Swift 4.0
let a: Range<Int> = 2..<5
for x in a {
print(x) // コンパイルエラー
}
これは、 Range<Double>
が数え上げられないのと同じです。 2.0...<5.0
に存在する Double
をすべて列挙するのは、実数という概念的には不可能ですし、有限個の浮動小数点数と考えても非現実的です。 Range<Int>
はたまたま数え上げられる Int
型の範囲を表しているだけで、 Range<Double>
と同様に範囲を表しているだけに過ぎません。
範囲を表すだけであれば、数値でなくても値の順序関係さえ決められれば( Comparable
なら) OK です。たとえば、 Range<String>
を考えることもできます。
let a: Range<String> = "cat" ..< "dog"
// 辞書順で "cat" から "dog" の間に存在するか
a.contains("cow") // true
a.contains("sheep") // false
Range<String>
で文字列の範囲を考えることには意味がありますが、 "cat" ..< "dog"
の範囲の文字列をすべて列挙することはできません。その特性の違いを表現するために CountableRange
が存在しているのです。
加えて、 Swift の Range
には upperBound
を含むか、含まないか、存在しない( lowerBound
だけが存在する)かというバリエーションがあります。これと Countable
を組み合わせると型がかけ算で増えてしまいます。そのかけ算の結果が先程の Range
ファミリーの一覧です。しかし、本当にこのバリエーションは必要なのでしょうか?
よくよく考えてみると、 Range<Bound>
が Countable
かどうかという性質は Bound
の型によって決まるはずです。 Int
なら列挙できますが、 Double
や String
だと列挙できません。そうだとすると、わざわざ Range
と CountableRange
という別の型を作らなくても、型パラメータが Int
なら列挙可、 Double
や String
なら列挙不可と、型パラメータによって挙動が変わってくれた方が便利です。
つまり、 Range<Int>
だと for
ループでイテレートできるけど Range<String>
ではできないというようなことを実現したいということです。
// これを実現したい
let a: Range<Int> = 2..<5
for x in a { // ✅ OK
...
}
let b: Range<String> = "cat" ..< "dog"
for y in b { // ⛔ NG
...
}
for
ループでイテレートできるためには Sequence
プロトコルに適合しなければなりません。 Range
の型パラメータが離散的で有限のものの場合には Range
に Sequence
に適合してほしい、これはまさに Conditional Conformance です。
Conditional Conformance でそれを実現するための PR がこちらです。
ここで Conditional Conformance を使って Range
を CountableRange
相当のものにしています。
extension Range: Sequence
where Bound: Strideable, Bound.Stride : SignedInteger {
...
}
...
extension Range: Collection, BidirectionalCollection, RandomAccessCollection
where Bound : Strideable, Bound.Stride : SignedInteger
{
...
}
残念ながら、この PR による変更は Swift 4.1 には含まれていません。 Swift 4.2 から 使えるようになります。
これによって CountableRange
などの冗長な型は取り除かれ、 Range
だけで↓が実現できるようになります。
// Swift 4.2
let a: Range<Int> = 2..<5
for x in a { // ✅ OK
...
}
let b: Range<String> = "cat" ..< "dog"
for y in b { // ⛔ NG
...
}
もう、 Range<Int>
を CountableRange<Int>
に変換するボイラープレートに悩まされることもありません。
let a: Range<Int> = ...
// Swift 4.0
for x in CountableRange(a) { ... }
// Swift 4.2
for x in a { ... }
僕がこれを待ち望んでいたのは、 Range
ファミリーを引数にとる関数やメソッドの実装が楽になるからです。
僕が作っている EasyImagy https://github.com/koher/EasyImagy というライブラリでは、 subscript
を使って簡単に画像のピクセルにアクセスできるんですが、
let image = Image<RGBA<UInt8>>(named: ...)!
let pixel = image[x, y]
print(pixel.red)
array[2..<5]
のようにして ArraySlice
を得られるように、 subscript
と Range
を組み合わせて簡単に画像の一部を切り出せます。
// x が 100 から 200 まで、 y が 300 から 400 までの
// 100x100 の大きさの部分画像を取得
let slice = image[100..<200, 300..<400]
しかし、この subscript
にあらゆる種類の Range
を渡せるようにしようとすると、すべての Range
型の組み合わせに対応する必要があります。その結果、こんなことになってしまいました。
このコードは Swift 3 の頃のものなので PartialRange
には対応していません。しかし、それでも 4 * 4 = 16 種類もの subscript
を実装しています。もし、この頃に Conditional Conformance があれば、 Countable
系の Range
が消えて、 2 * 2 = 4 種類の subscript
で済んだはずです。
結局これについては Swift 4 の RangeExpression
プロトコルで抽象的に実装できるようになったので、 Conditional Conformance の恩恵を受けることはなくなりました。
しかし、冗長気味だった Range
ファミリーが Conditional Conformance で整理されるのは喜ばしいことです。
自作の型と Conditional Conformance
Conditional Conformance は、何も標準ライブラリを改善するためだけのものではありません。自作の型を作るときにも役立ちます。先程紹介した EasyImagy を例に説明します。
2x2 のグレースケール画像を縮小して 1x1 にすることを考えてみましょう。
// グレースケール画像は各ピクセルが 0 から 255 までの値を取るので
// Image<UInt8> として UInt8 でピクセル一つを表す
let image = Image<UInt8>(width: 2, height: 2, pixels: [
10, 20,
30, 40,
])
各ピクセルの平均をとって (10 + 20 + 30 + 40) / 4
、つまり 25
という輝度値を持った 1x1 の画像が出来上がるはずです。
let resized = image.resizedTo(width: 1, height: 1)
print(resized[0, 0]) // 25
平均を取るためには足し算と割り算ができる必要があります。しかし、 Image<Pixel>
の型パラメータ Pixel
には任意の型を入れられるので、いつでも平均を計算できるとは限りません。
もし Image<Pixel : Numeric>
と限定してしまえばいつでもピクセル値の平均を計算できることになります。しかし、そうすると Image<Character>
でアスキーアートのような画像を表したり、 Image<UInt8?>
で欠損のある画像を表現するようなことができなくなってしまいます。
そんなときでも Swift なら extension で解決できます。 Numeric
な Pixel
を持つ Image
に対してのみ resizedTo
を実装すればよいのです。
extension Image where Pixel : Numeric {
func resizedTo(width: Int, height: Int) -> Image<Pixel> {
...
}
}
さて、グレースケール画像であれば Image<UInt8>
でよいですが、カラー画像を扱うには Image<RGBA<UInt8>>
のように型パラメータがネストした状態になります。この RGBA
も Numeric
でないと平均が計算できません。
Image<Pixel>
の Pixel
が任意の型をとれるように、 RGBA<Channel>
の Channel
にも任意を型を設定できるようにしたいところです。そうすると、 Channel
が Numeric
のときだけ RGBA
も Numeric
にしたい、そんな気持ちになってきます。
そう、そういうときは Conditional Conformance です!
extension RGBA : Numeric where Channel : Numeric {
...
}
これによって、平均が計算できる Image<RGBA<UInt8>>
はリサイズできるけど、 Image<RGBA<Bool>>
はリサイズできないということが、コンパイル時にチェックできるようになります。
let a: Image<RGBA<UInt8>> = ...
let resizedA = a.resizedTo(width: w, height: h) // ✅ OK
let b: Image<RGBA<Bool>> = ...
let resizedB = b.resizedTo(width: w, height: h) // ⛔ NG
リサイズだけでなく、回転や補間、畳込みでも同様のことが言えます。
Conditional Conformance がなかった頃にこれに近いことを実現しようとすると大変でした。これまでは、よくあるピクセル型についてそれぞれ resizedTo
等のメソッドをオーバーロードして実装していました。
// よく使う Pixel 型に対してひたすらオーバーロード😂
extension Image where Pixel = UInt8 { func resizedTo(...) ... }
extension Image where Pixel = UInt16 { func resizedTo(...) ... }
extension Image where Pixel = UInt32 { func resizedTo(...) ... }
...
extension Image where Pixel = RGBA<UInt8> { func resizedTo(...) ... }
extension Image where Pixel = RGBA<UInt16> { func resizedTo(...) ... }
extension Image where Pixel = RGBA<UInt32> { func resizedTo(...) ... }
...
当然、こんなことを手作業でやってられないので、 gyb ( Swift コンパイラで使われているツール)でコード生成していました。
% for pixel_type in pixel_types:
extension Image where Pixel = ${pixel_type} { func resizedTo(...) ... }
% end
しかし、 Swift 4.1 で念願の Conditional Conformance を手に入れて修正したのがこちらの PR です。「 +997 −2,852 」です!
しかも、 + のそこそこ大きな割合を specialize やファイル間のコードの移動が占めているので、実質的には関連コードの 8 割くらいを削減できたんじゃないかと思います。
// specialize の例
@_specialize(exported: true, where Self == Image<UInt8>, Kernel == Image<Int>)
@_specialize(exported: true, where Self == Image<UInt8>, Kernel == ImageSlice<Int>)
@_specialize(exported: true, where Self == ImageSlice<UInt8>, Kernel == Image<Int>)
@_specialize(exported: true, where Self == ImageSlice<UInt8>, Kernel == ImageSlice<Int>)
@_specialize(exported: true, where Self == Image<UInt16>, Kernel == Image<Int>)
@_specialize(exported: true, where Self == Image<UInt16>, Kernel == ImageSlice<Int>)
@_specialize(exported: true, where Self == ImageSlice<UInt16>, Kernel == Image<Int>)
@_specialize(exported: true, where Self == ImageSlice<UInt16>, Kernel == ImageSlice<Int>)
...
これはうれしい!!
そんなわけで、自作の型を実装してるときも Conditional Conformance 最高です。
まとめ
- Swift 4.1 から Conditional Conformance が使えるようになった
- 標準ライブラリが便利になった
-
Foo : Equatable
のとき、[Foo]
がEquatable
になった -
Foo : Codable
でない とき、[Foo]
がCodable
でなくなった - Swift 4.2 から
CountableRange
等がRange
等に統合される
-
- 自作の型でも Conditional Conformance は活躍
- コード生成でがんばってた箇所のコードを 8 割ほど削減できた
-
Proposal では "Implemented (Swift 4.2)" となっていますが、これは Proposal の内容がフルにサポートされるのが 4.2 からということで、 Conditional Conformance 自体は 4.1 から利用できます。 ↩