TL;DR
そうです、:
を使った場合、制約が満たされてなかったら補完リストに出ませんが、==
を使う時は制約が満たされてなくても補完リストに出ます(当然コンパイルエラーにはなりますが)。理由は不明。エロい人教えてください。
細かい話
拡張は Swift で非常に頻繁に使われる機能の一つですが、特定な状況を拡張したい、例えば Array
の Element
が String
であるのみの時に使われる joined(separator: String) -> String
機能はこんな感じで定義されています:
extension Array where Element == String {
public func joined(separator: String = default) -> String
}
このように、Swift では extension
の後ろに where A == B
と言うように制約をつけることができますが、where
の書き方はここでは ==
を使いましたが、他に :
を使うこともできます。同じように見えますが、意味として微妙に違います。==
の場合は 「B
である」ことを意味しますが、:
の場合は「B
と見なせる」場合を意味します。
そして、実はどんな状況でも両方使えるわけではありません、==
のみ使える場合と、:
のみ使える場合があります。具体的にはこんな感じです
B の型 |
== |
: |
---|---|---|
class |
⭕️ | ⭕️ |
struct |
⭕️ | ❌ |
protocol |
❌ | ⭕️ |
ご覧のように、==
と :
が両方使えるのは B
が class
のみの時です。なぜかと言うとまさに上記のニュアンスの違いがあって、B
が class
の場合は「その class
である」場合と、「その class
のサブクラスである」時の区別がありますが、struct
は継承の概念がないので「その struct
である」場合だけしかないですし、逆に protocol
の場合は protocol
自体は何か特定な型ではないので何かしらの型に落とし込む必要があるため、「その protocol
である」ことは意味がないのです。
ちなみに、両方使える class
の場合は上記通り、==
を使う場合と :
を使う場合は微妙に動きが違う時があります。それは protocol
に対する extension
で、A
が Self
の時:
protocol Some {}
extension UIView: Some {}
extension Some where Self: UIView {
func test() { print("works") }
}
UIView().test() //works
UIImageView().test() //works
protocol Some {}
extension UIView: Some {}
extension Some where Self == UIView {
func test() { print("test") }
}
UIView().test() //works
UIImageView().test() //error: 'UIImageView' is not convertible to 'UIView'
こうなる理由はとてもわかりやすいですね、==
は である
を意味します。そして UIImageView
は UIView
を継承しているであって UIView
ではないのです。だから :
を使うと test()
は普通に通りますが ==
を使うと test()
がエラーになります
ところが、extension Array where Element == SomeClass
の場合は、なぜか SomeClass
のサブクラスでも通ります。ちょっと不思議な気分です🤔
でもこれも気になるのですが、今日一番気になるのはやはり TL;DR に書いてある補完の問題ですね。なぜか :
で書くとちゃんと補完は正しいものにしか出てこないのですが ==
で書くと適合していない場合でも補完に出ます。コンパイルエラーになるのに。
ちなみに、なぜこの挙動に気づいたかと言うと、自分で NotAutoLayout という Auto Layout を殺す脱 Auto Layout のフレームワークを作っているのですが、中にこのように LayoutMaker
という型を作って、Phantom Type っぽい挙動で特定な場合には特定なヘルパーメソッドを作りたかったのですが、なぜか補完リストには条件が合わない時のメソッドもズラッと出てきて、非常に不親切だなぁと思ってそうならないように結局渋々すごいたくさんの XxxLayoutMaker
型を作りました。ところで今日 Phantom Type についてこに記事を読んで、「あーでも .shout()
は最初から補完リストに出るんだよなぁ」と思いながら Playground にソースコードコピペしたら出てこなかった!「ん?」って思って色々試してみた結果、その記事では :
を使っているから補完リストが正しく動いたことが判明しました。
しかし、上の表にも書いてある通り、struct
を使った場合はそもそも :
が使えないので、class
を使う必要があるんですよね…ところが class
を作った場合、仮に final
をつけたとしても、生成コストは struct
より高いのであんまり使いたくない感 is ある…無限に protocol
生やすしかないのかな(白目