2016/03/11

Swift 2.1のジェネリクスでできないことまとめ

個人的に開発中によく遭遇したジェネリクスでのエラーあたりについてまとめてみました。

なお、Swiftコンパイラテストによる、実際にコンパイルできる/できない例は次のテストケースを参照してください。

  • https://github.com/apple/swift/blob/a048b078e37dfafc0e188bb8c6f3f50f5f796494/test/decl/ext/generic.swift

TL;DR

  • ジェネリックパラメータは制限がきびしい
    • 制約では==は使えない
    • 準拠要求には非プロトコル型は使えない
  • 要求節と継承節を同時につけられない
  • Selfはプロトコル拡張でしか使えない
  • 内部クラスをジェネリッククラスにしたり、ジェネリッククラスに内部クラスを持たせたりできない

用語

まずはエラーメッセージ内でよく出てくる単語をまとめてみます。

  • requirement-clause: (要求節) where節以下の定義のこと
  • constraints: (制約) エラーメッセージ中では、requirement-clauseをこう呼ぶこともあるようだ
  • same-type requirement: (同値要求) ==を使った制約のこと。==の両辺には型パラメータ、プロトコル型、クラス型、構造体型などが使える。
  • conformance requirement: (準拠要求) :を使った制約のこと。左辺には型パラメータ、右辺にはプロトコル型とクラス型のみ許される (型パラメータと構造体型は不可)。
  • inheritance clause: (継承節) 拡張のprotocol継承のこと。
  • associated type: (連想型) プロトコル定義中の=代入のないtypealiasのこと。Swift 3からはassociatedtypeとなる予定
  • generic parameter: (ジェネリックパラメータ) ジェネリクスのパラメータのこと

実際のクラスなどに当てはめてみると次のようになります。

extension Collection
          : SomeProtocol         // inheritance clause
          where                  // ↓ ここから requirement clause (constraints)
          Element: Comparable,   // conformance requirement
          Element == Int         // same-type requirement
                                 //    ここまで requirement clause (constraints)
          { }

Protocol SomeProtocol {
    typealias SomeType           // associated type
}

struct SomeStruct
          <Element>              // generic parameter
          { }

struct OtherStruct
          <
          Element: Compatible    // conformance requirement
          where                  // ↓ ここから requirement clause (constraints)
          Element: Equatable     // conformance requirement
          >                      //    ここまで requirement clause (constraints)
          { }

できないこと

ジェネリックパラメータに==を使うこと

下の2つの項より、実質的にジェネリクスのパラメータに==を使える状況がないと思われます。

ジェネリックパラメータにを非ジェネリックにしてしまうこと

ジェネリックパラメータは、==を使った制約でジェネリックでない (non-generic) ようにすることはできません。

// error: same-type requirement makes generic parameter 'Element' non-generic
extension Array where Element == Int { }

プロトコルも非ジェネリックと扱われるので、同じエラーになります。

// error: same-type requirement makes generic parameter 'Element' non-generic
extension Array where Element == Comparable { }

なお、ジェネリックパラメータには継承節が使えるので、それを利用して書きましょう。上の例は次のように書けばOKです。

extension Array where Element: Comparable { }

また、プロトコルのtypealiasは当然ながらジェネリックパラメータではありません。 そのため、プロトコル拡張などでは==を使えます。

extension CollectionType where Index == UInt64 { }
extension GeneratorType where Element == Hashable { }

ジェネリックパラメータを同値関係にしてしまうこと

ジェネリクスのパラメータは、他のジェネリックパラメータとの同値関係に対して使うこともできません。

struct Foo<T, U> {
    let t: T
    let u: U
}
// error: same-type requirement makes generic parameters 'T' and 'U' equivalent
extension Foo where T == U { }

もちろん、プロトコルのtypealiasならOK。

protocol SomeProtocol {
    typealias A
    typealias B
}
extension SomeProtocol where A == B { }

ジェネリックパラメータに非プロトコル型の準拠要求をつけること

継承節での継承で、プロトコル型とクラス型以外のものを指定するとエラーになります。

// error: type 'Element' constrained to non-protocol type 'Int'
extension Array where Element: Int { }

// error: type 'Element' constrained to non-protocol type 'String'
extension Array where Element: String { }

プロトコル型とクラス型の継承は付けることができます。

extension Array where Element: Hashable { }
extension Array where Element: NSObject { }

なお、ジェネリックパラメータも非プロトコル型の扱いでエラーになります。

struct Foo<T, U> {
    let t: T
    let u: U
}

// error: type 'T' constrained to non-protocol type 'U'
extension Foo where T: U { }

制約つきの拡張にプロトコル型の継承をつけること

制約と継承を同時につけることはできません。

// error: extension of type 'Array' with constraints cannot have an inheritance clause
extension Array : SomeProtocol where Element: Comparable { }

これはプロトコル拡張の場合でも同様です。

protocol SomeProtocol {}

// error: extension of protocol 'GeneratorType' cannot have an inheritance clause
extension GeneratorType: SomeProtocol where Element: Comparable { }

ただし、プロトコル拡張だとSelfが使えるので、継承を使わずに次のように書くことができます。

extension GeneratorType where Self: SomeProtocol, Element: Comparable { }

プロトコル以外の拡張でSelfを使うこと

プロトコル及びメソッドの戻り値に対してのみ、Selfを使うことができます。それ以外ではエラーになります。

// error: 'Self' is only available in a protocol or as the result of a method in a class; did you mean 'Array'?
extension Array where Self: SomeProtocol, Element: Comparable { }

ちなみに、QuickFixに従って次のように修正してもエラーになります。Array内にArrayというジェネリックパラメータや連想型がないからです。

// error: type 'Array' in conformance requirement does not refer to a generic parameter or associated type
extension Array where Array: SomeProtocol, Element: Comparable { }

この場合には参照できるようなプロトコルがあればそれを使うくらいしか手がないでしょう。

extension _ArrayType where Self: SomeProtocol, Element: Comparable { }

どうしてもクラスを制約したければ、妥協するしかありません。

extension Array: SomeProtocol {}

内部クラスをジェネリッククラスにすること

内部クラスをジェネリッククラスにすることはできません。

// error: generic type 'SS' nested in type 'S' is not allowed
struct S {
    struct SS<T> {}
}

ジェネリッククラスを外に出してあげましょう。

struct S {}
struct SS<T> {}

ジェネリッククラスに内部クラスをつくること

ジェネリッククラスに内部クラスをつくることもできません。

// error: type 'SS' nested in generic type 'S' is not allowed
struct S<T> {
    struct SS {}
}

こちらも、ジェネリッククラスを外に出してあげましょう。

struct S<T> {}
struct SS {}

拡張に<>を使うこと

C++なんかを使っている人だとジェネリクスパラメータをArray<Int>みたいに特殊化したくなりますが、これはエラーになります。

// error: constrained extension must be declared on the unspecialized generic type 'S' with constraints specified by a 'where' clause
struct S<T> {}
extension S<Int> {}

指示通りにwhereを使ったrequirement節に書きかえてください。ただし、Intはプロトコル型でもクラス型でもありませんのでエラーになります。

// error: type 'T' constrained to non-protocol type 'Int'
extension S where T: Int {}

少し違いますが、IntegerTypeを使うならOK。

extension S where T: IntegerType {}

プロトコルで連想型での要求に自分自身を用いること

プロトコル内の連想型での要求部分に自分自身を用いることはできません。

// error: type may not reference itself as a requirement
protocol P {
    typealias S: P
}

いろいろな制限はSwift 3.0以降で緩和されていくかも

今月にSwift-Evolutionメーリングリスト内で、AppleのSwiftのリードエンジニアであるDouglas Gregor氏によるManifesto: Completing Genericsという投稿がありました。

これはジェネリクスに対する改善提案です。 上の問題のいくつかを解決するような提案もなされています。

提案段階で、実現するかどうかもわかっていない (早くてもSwift 3.0以降らしい) ですが、この中からおもしろそうなものをいくつかピックアップして紹介すると、

  • プロトコル内にデフォルト実装を書けるように (わざわざ拡張で書く必要がなくなる!)

  • protocol<…>Any<…>に変更

  • 文法を見やすく (whereを後ろにもっていく)

    func containsAll<S: Sequence>(elements: S) -> Bool
           where Sequence.Iterator.Element == Element
    
  • Generic typealiases

    typealias StringDictionary<Value> = Dictionary<String, Value>
    
  • Variadicなジェネリクス

    public struct ZipIterator<... Iterators : IteratorProtocol> : Iterator { ... }
    
  • パラメータ付き拡張

    extension<T> Array where Element == T? { ... }
    
  • associatedtypeに任意を制約を付けられるように

    associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element
    
  • ジェネリックパラメータのデフォルト値

    public final class Promise<Value, Reason=Error> { … }
    

提案段階なので文法が実際どうなるかはわかりません。 これ以外にもいろいろな改良があるようでこれからのジェネリクスまわりの変化が楽しみですね。

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。