protocolでよくわからないことがある [Swiftプログラミング]
まだまともなアプリに使えないでいるけど、Swiftはしょぼしょぼと勉強を続けている。よくわからないことがでてきた....
たとえばComparableをadoptした簡単な構造体
CやObjective-Cではヘッダを見ればわかるんだけど、Swiftにはヘッダはない。コンパイラが見えるところにstandard libraryの定義のあるファイルがあるはずだと思うんだけど、どこを見ればいいのかわからない。なんだか気持ち悪い。
コンパイラはヘッダみたいな回りくどい方法じゃなくて解析木みたいなかたちで持ってるんだろうけど、どうなってるんだろう。人間は何を見ればいいんだろう。
どう考えればいいのか、誰か教えて。githubに全部あるんだから見ろよ、と言われるだけかな....
もうひとつ別の疑問。たとえばこんなprotocolを定義する。
しかし、forの実行時にどのsayHello()の実装を呼べばいいかなんでわかるんだ?
Objective-Cのインスタンスだったら先頭にisaポインタがあって、これが何のクラスのインスタンスなのかを表していた。そのおかげでオブジェクトが何なのか判断してどのメソッドを呼ぶべきかを実行時に決めることができた。ところがSwiftの構造体にはそんな領域はない。
実際に構造体の定数インスタンスを4つ作ったところでメモリを見てみると で定数「a0」にpropertyの8バイトが確保されているだけでそのすぐ後ろには定数「a1」が詰まっていることがわかる(x86_64はLittle endian。ちなみに文字列定数はメモリを見る限りなんだかすごく複雑な構造をしてる。15バイトを超えるかどうかで確保の仕方も違うらしい)。Swiftは可能な限り型をコンパイル時点で確定させて実行時には(isaのような)余分な情報は持たないようにしていて、確かにその通りになってる。
定数arrayを宣言してるときに型は決まってるわけだから、それから解決してるということか? でもそうすると配列は要素と型の情報を実行時に持っていないといけない。しかしそれは変だよな。だってprotocolでなく型を指定して配列を宣言していれば呼ぶべき関数はコンパイル時点で決まって、そんなことする必要はないもんな。
不思議だ。僕の頭がまだObjective-Cのままだ、ということでもあるんだろうけど、ようするにSwiftでのprotocolがどうなってるかが理解できてないということだな。実装のイメージがあればわかるようになるんだろうけど、難しい....
たとえばComparableをadoptした簡単な構造体
struct Seq2 : Comparable { var first, second : Int static func <(lhs: Seq2, rhs: Seq2) -> Bool { return lhs.first < rhs.first || lhs.second < rhs.second } }二つの整数プロパティを持っていて、orでくくられて大きさを決める。プロトコルにあるオペレータのうち「<」だけを定義してある。「==」はRequiredなんだけどない。これを評価すると
var a = Seq2(first : 1, second : 2) var b = Seq2(first : 1, second : 1) print(a > b) print(a == b)定義していない「>」や「==」も動作する。「>」が動くのはComparable protocolにデフォルトの実装がextensionとしてあるからなんだろうけど、「==」が動くのはプロパティが整数だからなのか、そうするとEquatable protocolにプロパティを比較するデフォルト実装があるのか? どうなっているのかよくわからない。
CやObjective-Cではヘッダを見ればわかるんだけど、Swiftにはヘッダはない。コンパイラが見えるところにstandard libraryの定義のあるファイルがあるはずだと思うんだけど、どこを見ればいいのかわからない。なんだか気持ち悪い。
コンパイラはヘッダみたいな回りくどい方法じゃなくて解析木みたいなかたちで持ってるんだろうけど、どうなってるんだろう。人間は何を見ればいいんだろう。
どう考えればいいのか、誰か教えて。githubに全部あるんだから見ろよ、と言われるだけかな....
もうひとつ別の疑問。たとえばこんなprotocolを定義する。
protocol Helloable { func sayHello() }いい加減な名前だけどStrideableがあるぐらいだから。そして、このprotocolをadoptした二つの構造体
struct A : Helloable { var val: Int func sayHello() { print("hello from A, \(val)") } } struct B : Helloable { var val: String func sayHello() { print("hello from B, \(val)") } }それぞれひとつだけpropertyを持っていて、Helloableで定義された関数を実装している。このインスタンスをいくつか作って配列にする。
let a0 = A(val:0x0123456789ABCDEF) let a1 = A(val:0xAA) let b0 = B(val:"01234567") let b1 = B(val:"ABCDEFGHIJKLMNO") let array:[Helloable] = [a0, a1, b0, b1]配列の型指定にHelloable protocolだけを指定してる。これは許される。そして例えば順にsayHello()関数を呼ぶとする。
for c in array { c.sayHello() }この出力は
hello from A, 81985529216486895 hello from A, 170 hello from B, 01234567 hello from B, ABCDEFGHIJKLMNOとなる。ごく当たり前に見える。
しかし、forの実行時にどのsayHello()の実装を呼べばいいかなんでわかるんだ?
Objective-Cのインスタンスだったら先頭にisaポインタがあって、これが何のクラスのインスタンスなのかを表していた。そのおかげでオブジェクトが何なのか判断してどのメソッドを呼ぶべきかを実行時に決めることができた。ところがSwiftの構造体にはそんな領域はない。
実際に構造体の定数インスタンスを4つ作ったところでメモリを見てみると で定数「a0」にpropertyの8バイトが確保されているだけでそのすぐ後ろには定数「a1」が詰まっていることがわかる(x86_64はLittle endian。ちなみに文字列定数はメモリを見る限りなんだかすごく複雑な構造をしてる。15バイトを超えるかどうかで確保の仕方も違うらしい)。Swiftは可能な限り型をコンパイル時点で確定させて実行時には(isaのような)余分な情報は持たないようにしていて、確かにその通りになってる。
定数arrayを宣言してるときに型は決まってるわけだから、それから解決してるということか? でもそうすると配列は要素と型の情報を実行時に持っていないといけない。しかしそれは変だよな。だってprotocolでなく型を指定して配列を宣言していれば呼ぶべき関数はコンパイル時点で決まって、そんなことする必要はないもんな。
不思議だ。僕の頭がまだObjective-Cのままだ、ということでもあるんだろうけど、ようするにSwiftでのprotocolがどうなってるかが理解できてないということだな。実装のイメージがあればわかるようになるんだろうけど、難しい....
2019-01-19 19:00
nice!(0)
コメント(2)
==の明示的な実装がなくてもメンバがEquatableであればEquatableを満たすとされるようになったのです
https://github.com/apple/swift-evolution/blob/master/proposals/0185-synthesize-equatable-hashable.md
by ちくら (2019-02-16 23:17)
コメントありがとうございます。
なるほど、定型作業を減らすための思いやり仕様ということですね。確かにEquatableはボイラープレートになりがちだし、Hashableの真面目な実装は大変なので、必然性は理解できるというか、無かったらめんどくさかったです。
同じやり方でComparableを自動化するのは不可能というのも理解できました。なかなか面白いです。
Objective-Cでもボイラーブレートを減らす仕様があとから盛り込まれましたが、こういうのはSwiftならではですね。面白い、というかProtocolの考え方はロジカルで、個人的に僕とは相性が良さそうなのでぶりぶり使いこなせるようになりたいです。
by decafish (2019-02-17 10:45)