読者です 読者をやめる 読者になる 読者になる

Swiftでジェネリクスが使われているメソッドを理解する

ジェネリクスが分からない

BondっていうSwiftで関数型リアクティブプログラミングをするためのオープンソースがあります。試しに使ってみようと触ってるんですが、README通りに動かないのでライブラリのコードを眺め始めました。しかし、書いてあるコードが理解できない。。

読めないのは具体的にはこのコード。

public func map<S: Dynamical, T, U where S.DynamicType == T>(dynamical: S, f: T -> U) -> Dynamic<U>

もうどっからどこが引数でこいつが何を返すのかがよく分からない。mapのあとの<>は、ジェネリクスってのは分かってるんだけど、:がついてたり、whereとか出てきちゃってもうさっぱり。 ということで読めるように勉強スタートです。

f:id:masato47744:20150207211432j:plain

ジェネリクスへの誤解

ジェネリクスって、Array<Int>とか、Dictionary<String, Int>みたいなやつの<>の部分で、Arrayの中身が全てIntであるってこと、DictionaryのキーはStringで値はIntのように格納する値を制約するためのものぐらいに思ってました。だから、さっきの関数を見たときにSTUってなに?どっから出てきたのその英数字wwそれをどうやって渡すの?とか分からなくて思考停止しました。でいい説明記事ないかとあさっていた訳ですが、AppleのSwift公式ドキュメントのGenericsの説明を読んでみると、ジェネリクス関数と、ジェネリクスがあるってことがわかった。ああ、そうか、さっきのはジェネリクス関数か!ということが分かってそこから進めてみようということになりました。

ジェネリクス関数について理解する

Appleの公式ドキュメントに載ってる例(2つの値を交換する関数)がとても分かりやすいので、それを引用します。

// 1. Intしか交換できない
func swapTwoInts(inout a: Int, inout b: Int)
// 2. Doubleしか交換できない
func swapTwoDoubles(inout a: Double, inout b: Double)
// 3. 好きな型のものを交換できる
func swapTwoValues<T>(inout a: T, inout b: T)

1つ目と2つ目は指定された型のものしか受け取れない。じゃあ全ての型についてメソッド書くんか?っていうのは気が遠くなる作業です。そこで、3番目のジェネリクスを使った関数が出てくるわけです。Tというのは慣例的にTと表しているだけでTという型は宣言されているわけではありません。

そして関数を呼び出すときは.swapTwoValues(a, b)でよくて、別に.swapTwoValues<String>(a, b)みたいなことはしなくてOK。

ただ、ここで一つ疑問でどんな型でもいいっていうなら、こんな風にAnyObject使えばいいんじゃない?って思うんだけど、これだと、aとbの型になんでも入れられてしまうよね。だからジェネリクスってのが複数の問題を解決する訳です!

// 例えば、aにInt、bにStringを入れられてしまう!
func swapTwoValues(inout a: AnyObject, inout b: AnyObject)

ここでもう一度読めなかった最初の関数を読んでみる

ひとまずいまの知識で言えば、読めなった最初の関数は以下のように理解できる。

public func map<S: Dynamical, T, U where S.DynamicType == T>(dynamical: S, f: T -> U) -> Dynamic<U>
  • mapという名前のメソッド名(public func mapの部分)
  • S型のdynamicT型の引数をとってU型を返す関数fを引数にとる(dynamical: S, f: T -> U)の部分)
  • 内部でU型が使われるDynamic型を返す(-> Dynamic<U>の部分)

ただ、やっぱり、<S: Dynamical, T, U where S.DynamicType == T>の部分が分かりません。。

型制約(Type Constraint Syntax)

Swiftの文書を読んでくとまさにこのことが書いてありました。これもまた分かりやすいので引用します。

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // function body goes here
}

こんな風に引数にsomeTと、someUを受け取る関数がありますが、<T: SomeClass, U: SomeProtocol>は引数に渡すsomeTはSomeClassのサブクラスである必要があって、someUはSomeProtocolに適合してる必要があるってことを表してます。まさに、制約するということですね。

さらにもう一度読めなかった最初の関数を読んでみる

public func map<S: Dynamical, T, U where S.DynamicType == T>(dynamical: S, f: T -> U) -> Dynamic<U>
  • mapという名前のメソッド名(public func mapの部分)
  • S型のdynamicT型の引数をとってU型を返す関数fを引数にとる(dynamical: S, f: T -> U)の部分)
  • 内部でU型が使われるDynamic型を返す(-> Dynamic<U>の部分)
  • S型はDynamicalプロトコルに適合している必要がある(S: Dynamicalの部分)

さっき分からなかった、S: Dynamicalの部分が読めるようになりました。ただ、やっぱり、U where S.DynamicType == Tの部分が分からない。Sって型のはずなのに、なんで、.で接続してるんすか。。そして、USTの関係は。。乞うご期待みたいな感じで意味不明。ということでさらに公式ドキュメントを読み進めます。

関連型(Associated Types)

まずは、U where S.DynamicType == TのうちのS.DynamicTypeの部分です。型にドットつけるなんてどういうことやって思いましたが、関連型という項で説明されていました。 関連型というのは、Protocolでジェネリクスを使いたい場合に使うもののようです。Protocolでジェネリクスが使いたくなる場面のサンプルコードを引用します。

protocol Container {
    typealias ItemType
    mutating func append(item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}

このサンプルコードでは、Containerプロトコル名、typealias ItemTypeが関連型です。そもそも、Protocolでジェネリクスを使う場合protcol Container<T>でいいんじゃないの?って思うんですが、それができないようです。なんでできないかは言葉では説明できないんですが。。

それで関連型については、ドット接続で表現できるということ。まさに、S.DynamicTypeのことです。実際にBond.swiftで、Dynamicalプロトコルを見てみると

public protocol Dynamical {
  typealias DynamicType
  func designatedDynamic() -> Dynamic<DynamicType>
}

おおーー、DynamicTypeあった!つまり、S.DynamicType == Tの部分は、Dynamicalプロトコルに適合しているS型の関連型のDynamicTypeの型はTと同じ型である。ということを表しているわけですね。

Where Clauses

そして最後の難関のwhereです。これもきちんとAppleのドキュメントに書いてありました。

ぼくはてっきり、U where S.DynamicType == Tのように、whereはUにかかってるものだと思ってたんですが、違いました。ジェネリクスの全体にかかってました。 つまり、<S: Dynamical, T, Uwhere S.DynamicType == T>に分けて考える。whereはUにかかってるんじゃなくて、S, T, U全体にかかってる。まさにSQLのWhere句のような考え方ですね。S, T, Uという型を使うんだけど、ただし、SのDyanamicTypeの型はTの型と同じでなければならないということを制約しているわけですね。そして、U型には何の制約はかかっていません。

これらを総合すると以下のようになるわけです。やっと読めました!!

public func map<S: Dynamical, T, U where S.DynamicType == T>(dynamical: S, f: T -> U) -> Dynamic<U>
  • mapという名前のメソッド名(public func map)
  • S型のdynamicT型の引数をとってU型を返す関数fを引数にとる(dynamical: S, f: T -> U))
  • 内部でU型が使われるDynamic型を返す(-> Dynamical<U>)
  • ただし、S型はDynamicalプロトコルに適合している必要がある(S: Dynamicalの部分)
  • さらに、S型の関連型のDynamicTypeの型はTと同じ型である必要がある(S.DynamicType == Tの部分)

まとめ

なんていうか、ジェネリクスは、日本語にするとただしとすると、なんかしっくりくるような気がします。てっきりパラメータ的な感じかと勘違いしてしまっていたけどここで理解できてよかったです。 これで、プルリクできそうです。

参考にした記事