LoginSignup
16
6

More than 5 years have passed since last update.

extension where T: SomeClass と where T == SomeClass の補完上の区別 & 原因求む

Last updated at Posted at 2017-11-07

TL;DR

下記の図をご覧ください
スクリーンショット 2017-11-07 23.52.16.png
.
スクリーンショット 2017-11-07 23.52.31.png

そうです、: を使った場合、制約が満たされてなかったら補完リストに出ませんが、== を使う時は制約が満たされてなくても補完リストに出ます(当然コンパイルエラーにはなりますが)。理由は不明。エロい人教えてください。

細かい話

拡張は Swift で非常に頻繁に使われる機能の一つですが、特定な状況を拡張したい、例えば ArrayElementString であるのみの時に使われる 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 ⭕️

ご覧のように、==: が両方使えるのは Bclass のみの時です。なぜかと言うとまさに上記のニュアンスの違いがあって、Bclass の場合は「その class である」場合と、「その class のサブクラスである」時の区別がありますが、struct は継承の概念がないので「その struct である」場合だけしかないですし、逆に protocol の場合は protocol 自体は何か特定な型ではないので何かしらの型に落とし込む必要があるため、「その protocol である」ことは意味がないのです。

ちなみに、両方使える class の場合は上記通り、== を使う場合と : を使う場合は微妙に動きが違う時があります。それは protocol に対する extension で、ASelf の時:

PatternA
protocol Some {}
extension UIView: Some {}
extension Some where Self: UIView {
    func test() { print("works") }
}

UIView().test() //works
UIImageView().test() //works
PatternB
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'

こうなる理由はとてもわかりやすいですね、==である を意味します。そして UIImageViewUIView を継承しているであって 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 生やすしかないのかな(白目

16
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
6