LoginSignup
20
5

More than 5 years have passed since last update.

Conditional Conformanceで遊ぼう

Last updated at Posted at 2018-12-23

Conditional ConformanceはSwift4.1で追加された言語機能です。
型パラメータに条件をつけて(Conditional)他のProtocolに適合する(Conformance)ことができる便利な機能です。

class Box<T> {
    var value: T
    init(_ value: T) { self.value = value }
}

解説用の箱です。これをConditional Conformanceで拡張して遊んでみましょう。最近私や身の回りの人が踏んだものを一通り紹介します。

前提となるProtocolは明示的に宣言する必要がある

// Conditional conformance of type 'Box<T>' to protocol 'Hashable' does not imply conformance to inherited protocol 'Equatable'
extension Box: Hashable where T: Hashable {
    func hash(into hasher: inout Hasher) {
        value.hash(into: &hasher)
    }
}

HashableはEquatableを前提に持つProtocolです。ConditionalConformanceでHashableを使う場合は、「明示的にEquatableのConditionalConformanceを宣言する」必要があります。

複数のConditionから同一のConformanceは作れない

// Conflicting conformance of 'Box<T>' to protocol 'Hashable'; there cannot be more than one conformance, even with different conditional bounds
extension Box: Hashable where T: AnyObject {
    func hash(into hasher: inout Hasher) {
        return ObjectIdentifier(self).hash(into: &hasher)
    }
}

// Conflicting conformance of 'Box<T>' to protocol 'Hashable'; there cannot be more than one conformance, even with different conditional bounds
extension Box: Hashable where T == Any.Type {
    func hash(into hasher: inout Hasher) {
        return ObjectIdentifier(self).hash(into: &hasher)
    }
}

一度HashableにConformしたBoxは他の方法でHashableにConformできなくなります。
滅多にこの要求は発生しないものですが、ObjectIdentifierを使って良しなに動かす夢は潰えました。

Existentialが作れる

extension Box: CustomStringConvertible where T: CustomStringConvertible {
    var description: String { return value.description }
}

func check(_ value: Any) {
    guard let value = value as? CustomStringConvertible else { return }
    print(value)
}

check(Box(1))
check(Box(Box(1)))

Swift4.2からExistentialが使えるようになりました。Swift4.1ではキャストの実行時に警告(warning: Swift runtime does not yet support dynamically querying conditional conformance)が出ていたものです。

Existentialを作るとクラッシュする

作れると言ったな?あれは嘘だ


extension Box: CustomStringConvertible where T: Sequence, T.Element :CustomStringConvertible {
    var description: String {
        return "[" + value.map { $0.description }.joined(separator: ", ") + "]"
    }
}

check(Box([1, 2, 3])) // EXC_BAD_ACCESS (code=EXC_I386_GPFLT

Conditionの複雑さが以下の条件を超えると実行時クラッシュです。

  • associatedtypeを持つprotocolに条件付をする
  • associatedtypeにも条件付をする

Bugsに報告されています https://bugs.swift.org/browse/SR-8666

typealiasはconditionalではなく、全体を汚染する。

数日前にDiscordで盛り上がっていたネタです。

extension Box: Sequence where T: Sequence {
    typealias Element = T.Element
    typealias Iterator = T.Iterator

    func makeIterator() -> T.Iterator {
        return value.makeIterator()
    }
}

例えばSequenceのConditional Conformanceを書いてみましょう。
一見すると問題なさそうですが、ElementはT: Sequenceでない場合にも存在してしまいます。いろいろ困ったことが発生します。

// error: Segmentation fault: 11
print(Box<Int>.Element.self)

Box.Elementは存在しているのですが、Int.Elementは存在しない、これはコンパイル時にセグフォが発生します。

extension Box: IteratorProtocol where T: IteratorProtocol {
    // Invalid redeclaration of 'Element'
    typealias Element = T.Element

    func next() -> Element? {
        return value.next()
    }
}

加えてIteratorProtocolのConditional Conformanceを追加しました。Elementは汚染されているので宣言することが出来ません。ところでこの場合は汚染されたElementはT.Elementなので、typealiasを消してそのまま利用すれば、「たまたま正しく」動きます。
極めて稀に発生する、ElementをT.Element以外で指定したいという要求が発生すると詰みます。他の方法を探しましょう。

Bugsに報告されています https://bugs.swift.org/browse/SR-9533

おわり

Conditional Conformanceで遊んでみました。残念ながら少し壊れてしまいました。
とはいえConditional Conformanceの用途の80%は「ElementがPならWrapperもPにしたい」というもので今回紹介した悪いコードを使う必要は殆どありません。でも20%ぐらいは壊しそうになること、ありますよね。

20%の具体例ですが @taketo1024 先生が書いているSwiftyMathが、限界を超えそうなConditional Conformanceを要所要所で利用されていて面白いです。
https://github.com/taketo1024/SwiftyMath

20
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
5