Swiftでジェネリクスが使われているメソッドを理解する
ジェネリクスが分からない
BondっていうSwiftで関数型リアクティブプログラミングをするためのオープンソースがあります。試しに使ってみようと触ってるんですが、README通りに動かないのでライブラリのコードを眺め始めました。しかし、書いてあるコードが理解できない。。
読めないのは具体的にはこのコード。
public func map<S: Dynamical, T, U where S.DynamicType == T>(dynamical: S, f: T -> U) -> Dynamic<U>
もうどっからどこが引数でこいつが何を返すのかがよく分からない。map
のあとの<>
は、ジェネリクスってのは分かってるんだけど、:
がついてたり、where
とか出てきちゃってもうさっぱり。
ということで読めるように勉強スタートです。
ジェネリクスへの誤解
ジェネリクスって、Array<Int>
とか、Dictionary<String, Int>
みたいなやつの<>
の部分で、Array
の中身が全てInt
であるってこと、Dictionary
のキーはString
で値はInt
のように格納する値を制約するためのものぐらいに思ってました。だから、さっきの関数を見たときにS
、T
、U
ってなに?どっから出てきたのその英数字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型のdynamic
とT型の引数をとって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型のdynamic
とT型の引数をとってU型を返す関数f
を引数にとる(dynamical: S, f: T -> U)
の部分)- 内部でU型が使われるDynamic型を返す(
-> Dynamic<U>
の部分) S型はDynamicalプロトコルに適合
している必要がある(S: Dynamical
の部分)
さっき分からなかった、S: Dynamical
の部分が読めるようになりました。ただ、やっぱり、U where S.DynamicType == T
の部分が分からない。S
って型のはずなのに、なんで、.
で接続してるんすか。。そして、U
とS
とT
の関係は。。乞うご期待みたいな感じで意味不明。ということでさらに公式ドキュメントを読み進めます。
関連型(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, U
とwhere 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型のdynamic
とT型の引数をとってU型を返す関数f
を引数にとる(dynamical: S, f: T -> U)
)- 内部でU型が使われるDynamic型を返す(
-> Dynamical<U>
) - ただし、
S型はDynamicalプロトコルに適合
している必要がある(S: Dynamical
の部分) - さらに、
S型の関連型のDynamicTypeの型はTと同じ型である必要がある
(S.DynamicType == T
の部分)
まとめ
なんていうか、ジェネリクスは、日本語にするとただし
とすると、なんかしっくりくるような気がします。てっきりパラメータ的な感じかと勘違いしてしまっていたけどここで理解できてよかったです。
これで、プルリクできそうです。