SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

アップルの新プログラミング言語「Swift」を探検しよう

Objective-Cより柔軟かつ安全なプログラミングを可能にするSwiftの「ジェネリクス」

アップルの新プログラミング言語「Swift」を探検しよう 第7回


  • X ポスト
  • このエントリーをはてなブックマークに追加

型引数宣言のルールと制約

 ジェネリック関数の宣言方法をもう一度見てください。

宣言方法
func 関数名<型引数のリストと制約>(引数のリスト) -> 返り値 {
    (処理)
}

 <型引数のリストと制約>部分で宣言されている型引数には、次のような記述も行えます。

  • 継承あるいは準拠しなければならないクラス・プロトコル
  • 型引数同士の関係(制約)

継承あるいは準拠しなければならないクラス・プロトコル

 <型引数のリストと制約>部分で次のように宣言することで、各型引数が、各型引数が特定のクラスそのものまたはサブクラスであるか、特定のプロトコルに準拠していることを要求できます。

宣言方法
<型引数1: (クラス名 or プロトコル名), ……, 型引数n: (クラス名 or プロトコル名)>

 前節で例に出したcreateEmpty関数でも、型引数に準拠すべきプロトコルを指定しました。ここでは、別の例で確認してみましょう。

func notEqual<T: Equatable>(first: T, second: T) -> Bool {
    return first != second
}
notEqual(1, 2) // true

 このnotEqual関数は、型引数としてEquatableプロトコルに準拠したものだけを扱うように要求しています。ここでEquatableに準拠しないクラスを別に定義しても、そのクラスのインスタンスを次の例のようにnotEqual関数の引数に渡すことはできません。

class Food {}
notEqual(Food(), Food()) // コンパイルエラー

 Equatableプロトコルは等値演算子(==)の実装を要求するプロトコルです。プロトコルに準拠した型は次の2項等値演算子を実装する必要があります。

func == (lhs: Self, rhs: Self) -> Bool

 演算子とプロトコルをともに扱う方法については演算子の章で説明します。また、プロトコルやクラスで用いるメタな型を表すSelfという記号については次回のメタタイプの章で説明します。

 これは型引数の後に続く型についての要求がプロトコルではなく、クラスだった場合も同様です。特定のクラスかまたはサブクラスでない型が型引数として推論される場合には、次のようにコンパイルエラーになります。

class Apple: Food {}
class Metal {}
func eat<T: Food>(food: T) {
}
eat(Food())
eat(Apple())
eat(Metal()) // コンパイルエラー

 クラスと関数を用いるケースはメソッドという代替手段があるため、実用的にこのようなコードを書くことはまれですが、プロトコルと関数については、公式のAPIでもジェネリクスを用いて引数の型に対する要求を行う関数が多く存在します。

where節での制約

 型引数のリスト宣言の後ろに記述するwhere節では、次のように前に宣言された引数同士の制約を記述できます。

宣言方法
<型引数のリスト where 型引数に関連した型: (クラス名 or プロトコル名), 型引数に関連した型 == 型名, ...>

 ここでの「型引数に関連した型」とは、具体的には以下の2つを指します。

  • リスト部で宣言された型引数そのもの
  • リスト部で宣言されたプロトコル準拠型引数の付属型
例1
protocol Readable {
    typealias IndexType
    typealias ContentType
    var index: IndexType { get }
    var content: ContentType { get }
}

struct Page: Readable {
    var index: Int
    var content: String
}

func readContentsFromTop<R: Readable where R.IndexType: Comparable>(var readables: [R]) {
    sort(&readables) { lReadable, rReadable in lReadable.index < rReadable.index }
    for readable in readables {
        println("read \(readable.content)")
    }
}

let pages = [Page(index: 6, content: "How to cook"), Page(index: 0, content: "Title")]
readContentsFromTop(pages) 
//read Title
//read How to cook

 readContentsFromTop関数は、引数で渡された配列の中身をindexの昇順に出力していきます。Readableプロトコルに準拠した型Rの配列の変数を、引数としています(変数を引数とする関数の宣言方法は本連載の第2回を参照してください)。

 Readableプロトコルに準拠した型引数Rの付属型には、where節でさらに次の制約が加えられています。

R: Readable where R.IndexType: Comparable

 これはRがReadableプロトコルに準拠しており、さらにその付属型IndexTypeはComparableプロトコルに準拠することを示しています。

 Comparableプロトコルは比較演算子(<)の実装を要求するプロトコルです。プロトコルに準拠した型は次の2項比較演算子を実装する必要があります。

func < (lhs: Self, rhs: Self) -> Bool

 readContentsFromTop関数の内部では、次のsort関数が使われています

func sort<T>(inout array: [T], isOrderedBefore: (T, T) -> Bool)

 この関数は第1引数にT型の配列をとり、第2引数に昇順降順を定めるクロージャをとります。実行されると、第1引数に渡された配列の中身が、第2引数に定められた昇順/降順に応じてソートされます(inoutキーワードは本連載の第2回を参照してください)。

 ここでは第2引数として末尾クロージャを用いていますが、R.IndexType型がComparableプロトコルに準拠しているという制約があるため、その中で次のように比較演算子を使用できています。

{ lReadable, rReadable in lReadable.index < rReadable.index }

 さらに、先ほどの例に加える形で次のように実装してみましょう。

例2
func equal<R1: Readable, R2: Readable where R1.IndexType == R2.IndexType, R1.ContentType == R2.ContentType, R1.IndexType: Equatable, R1.ContentType: Equatable>(lhs: R1, rhs: R2) -> Bool {
    return lhs.index == rhs.index && lhs.content == rhs.content
}

struct Paper: Readable {
    var title: String
    var index: Int
    var content: String
}

let page = Page(index: 1, content: "Thanks to wife")
let paper = Paper(title: "On mathemetics", index: 1, content: "Thanks to wife")
equal(page, paper) // true

 このequal関数は、Readableプロトコルに準拠した型を持つ2つの引数の等値判定を行います。型引数の部分はご覧のとおり、ずいぶん複雑です。

<R1: Readable, R2: Readable where R1.IndexType == R2.IndexType, R1.ContentType == R2.ContentType, R1.IndexType: Equatable, R1.ContentType: Equatable>

 この宣言では、型引数へ次のような要請がなされています。

 まず、型引数のリストに宣言されたR1、R2の型引数はともにReadableプロトコルに準拠したものでなければなりません。さらに、where節でReadableプロトコルに準拠した型引数の付属型については、次の3つが要請されています。

  • R1.IndexTypeとR2.IndexTypeが同じ型であること
  • R1.ContentTypeとR2.ContentTypeが同じ型であること
  • R1.IndexTypeと R1.ContentTypeがEquatableプロトコルに準拠していること

 結果的に、R2.IndexTypeとR2.ContentTypeがEquatableプロトコルに準拠していることも型推論によって導かれるため、関数の宣言内部での双方の引数のindexプロパティ、contentプロパティの型については等値演算子==を使ってReadableプロトコルとしての等値性を判定できることが分かります。

次のページ
ジェネリックタイプ

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
アップルの新プログラミング言語「Swift」を探検しよう連載記事一覧

もっと読む

この記事の著者

yad(ヤド)

クラスメソッド株式会社のアプリケーションエンジニア。iPhoneアプリケーションの開発に2年以上従事している。開発に使用するObjective-Cのみならず、関数型言語Haskellや機械学習などにも関心がある。「Developers.IO」に寄稿した記事の一覧

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/8449 2015/03/02 12:51

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング