Swift
swtws

Swift 4.1で導入されたConditional Conformanceのインパクト

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) // コンパイルエラー

最初のコードでは ab の型は [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 する)型は == を使えると知っている人も多いんじゃないかと思います。 IntString, Bool などは == で比較することができますが、これらの型はすべて Equatable に適合しています。

extension Int : Equatable {
    static func ==(lhs: Int, rhs: Int) -> Bool {
        ...
    }
}

しかし、さっき作った FooEquatable ではありませんでしたが、 == で比較することができました。一見、 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

しかし、自作した FooEquatable でないので、 [Foo] に対しては countElements を使うことができません。

let c = [Foo(value: 1), Foo(value: 1), Foo(value: 2), Foo(value: 3)]
countElements(in: c, equalTo: Foo(value: 1)) // コンパイルエラー

FooEquatable に適合させるには次のように実装します。

struct Foo { let value: Int }

extension Foo : Equatable {
    static func ==(lhs: Foo, rhs: Foo) -> Bool {
        return lhs.value == rhs.value
    }
}

そうすれば FooEquatable なので、 [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 の ArrayEquatable なのでしょうか?それとも 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 のように値型のエラーもあります。値型では === は使えません。無理やり DecodingErrorEquatable に適合させようとして、保持する値がすべて等しかったときに等しいことにしたとしても、別のタイミングで発生したたまたま同じに見えるエラーを同値とすることに何か意味があるでしょうか?

このように、 equality を定義すること自体に意味が見出だせない型も存在します。そのような型では無理に == の意味を定義するより、比較しようとしたらコンパイルエラーになるのが自然でしょう。

let a: DecodingError = ...
let b: DecodingError = ...
print(a == b) // コンパイルエラー

Objective-C や Java などの昔ながらのオブジェクト指向言語では、 isEqualhashNSObject のようなルートクラスに定義されており、必要に応じてこれをオーバーライドするようになっています。しかし、それでは本来 isEqualhash を持つべきでない型もそれらを使えることになってしまいます。静的型付言語なのに isEqualhash を持つべきかを型で表すことができず、適切にコンパイルエラーを出せないというのは敗北です。

そういう意味で、 EquatableHashable で明示的に ==hashValue が定義されるべきかを表現することができる Swift は、 Objective-C 等の言語と比べてより成熟した静的型付言語だと言えるでしょう。

DecodingErrorEquatable でないと言いました。そうであれば DecodingErrorArray である [DecodingError] も当然 Equatable でないと考えるのが自然です。要素が等しいかどうかを判定できないのに、その Array が等しいかどうかをどのように定義することができるでしょうか。

let a: [DecodingError] = ...
let b: [DecodingError] = ...
print(a == b) // コンパイルエラー

つまり、要素が Equatable なら ArrayEquatable に、要素が Equatable でなければ Array 自体も Equatable でなく なってほしいということです。

Conditional Conformance

要素が Equatable なら ArrayEquatable に、要素が Equatable でなければ Array 自体も Equatable でなく なってほしい

それをコードで表すと次のように書けます。 これが Conditional Conformance です。

extension Array : Equatable where Element : Equatable {
    ...
}

この例だと ElementEquatable かという条件( condition )によって ArrayEquatable に適合( conform )するかを決定します。なので Conditional Conformance と呼びます。

しかし、 Swift 4.0 までは Conditional Conformance がサポートされていませんでした。だからといって、 [Int] 同士を == で比較できないというのはあまりにも不便すぎます。そのため、 ArrayEquatable ではありませんが、次のような == 関数を別途提供することで [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 です。これまでの OptionalEquatable ではありませんでした。

たとえば、次のようなコードを書いた経験が一度はあると思いますが、 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 なんかにも影響があります。 XCTAssertEqualEquatable な値を受け取ってチェックを行います。しかし、 OptionalEquatable でないのでそのままでは 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

これまでは ArrayOptional のような具象型の、 Equatable の適合について見てきました。今度はプロトコル視点で、 Codable について見てみます。

Codable がおもしろいのは、 Conditional Conformance がない Swift 4.0 の時点ですでに ArrayOptionalCodable になっていることです。

なので、 [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) // ???

ArrayCodable なので当然コンパイルは通ります。

// Swift 4.0
import Foundation

struct Foo {}

let a = [Foo(), Foo(), Foo()]
try! JSONEncoder().encode(a) // コンパイル可

しかし、 Fooencode することはできないので実行時エラーになります。これをコンパイルエラーにできないのは静的型付言語としてイケてないですね。

// Swift 4.0
import Foundation

struct Foo {}

let a = [Foo(), Foo(), Foo()]
try! JSONEncoder().encode(a) // 実行時エラー

Swift 4.1 では Conditional Conformance で、要素が Codable なときだけ ArrayCodable になります。

// 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 で、 EquatableEquatable でない 側に、 CodableCodable である 側に倒されているということです。

== については [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 です。では RangeCountableRange の違いは何でしょうか?

Range は単に lowerBound から upperBound までの範囲を表すだけですが、 CountableRange はその間の値を数え上げることができる型です。たとえば、 2..<5 という CountableRange2, 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 なら列挙できますが、 DoubleString だと列挙できません。そうだとすると、わざわざ RangeCountableRange という別の型を作らなくても、型パラメータが Int なら列挙可、 DoubleString なら列挙不可と、型パラメータによって挙動が変わってくれた方が便利です。

つまり、 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 の型パラメータが離散的で有限のものの場合には RangeSequence に適合してほしい、これはまさに Conditional Conformance です。

Conditional Conformance でそれを実現するための PR がこちらです。

ここで Conditional Conformance を使って RangeCountableRange 相当のものにしています。

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 を得られるように、 subscriptRange を組み合わせて簡単に画像の一部を切り出せます。

// 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 で解決できます。 NumericPixel を持つ Image に対してのみ resizedTo を実装すればよいのです。

extension Image where Pixel : Numeric {
    func resizedTo(width: Int, height: Int) -> Image<Pixel> {
        ...
    }
}

さて、グレースケール画像であれば Image<UInt8> でよいですが、カラー画像を扱うには Image<RGBA<UInt8>> のように型パラメータがネストした状態になります。この RGBANumeric でないと平均が計算できません。

Image<Pixel>Pixel が任意の型をとれるように、 RGBA<Channel>Channel にも任意を型を設定できるようにしたいところです。そうすると、 ChannelNumeric のときだけ RGBANumeric にしたい、そんな気持ちになってきます。

そう、そういうときは 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 」です!

easy-imagy-conditional-conformance-pr.png

しかも、 + のそこそこ大きな割合を 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 割ほど削減できた

  1. Proposal では "Implemented (Swift 4.2)" となっていますが、これは Proposal の内容がフルにサポートされるのが 4.2 からということで、 Conditional Conformance 自体は 4.1 から利用できます。