@koher さんの記事、なぜSwiftのプロトコルはジェネリクスをサポートしないのかにおける議論は改めて記事にするだけの価値があるのでそうすることにします。
君の型は?
まずは以下のコードをご覧ください。
import Foundation
protocol Animal {
var binomen:String { get }
init()
}
struct Cat: Animal {
let binomen = "Felis silvestris catus"
let theYoung = "kitten"
}
struct Dog: Animal {
let binomen = "Canis lupus familiaris"
let theYoung = "puppy"
}
var pet:Animal = (arc4random() & 1 == 1) ? Cat() : Dog()
- Q0: このコードは動きますか?
- A0: 動く
- A1: (type mismatchで動かない)
正解はA0、きちんと動きます。PlaygroundsなりREPLで確認してみてください。で、本題です。
- Q1:
pet
の型は?- A0:
Cat
かDog
か動的に決まる - A1: 何らかの型に静的に決まる
- A0:
正解はA1。そして型の名はCat
でもDog
でもないAnimal
です。
Playgroundの型インスペクターを見てももろそう書いてありますし、.binomen
にアクセスできても.theYoung
にはアクセスできません。どちらにも.theYoung
は存在していますがAnimal
には知る由もないのです。
let 🐱 = Cat()
🐱.theYoung // "kitten"
let 🐶 = Dog()
🐶.theYoung // "puppy"
ならば
pet = Animal()
も動きそうなものですが、protocol type 'Animal' cannot be instantiated
と怒られてしまいます。あくまで具現化(instantiation)はstruct
かenum
かclass
で、protocol
は本来の「規約」として使えということですね。
そのことがわかれば、Swiftの総称型(generics)の設計もわかります。
全ての定数(let
)と変数(var
)の型はコンパイル時に静的に決まる
ほんと、これだけです。なぜ
func ditto<T>(x:T)->T { return x }
がOKなのに
let ditto<T>:(T)->T = { $0 }
はNGなのか。まさにこれが理由です。T
に何が入るのかは、コンパイル時に決まってなければならないから。実行時に決まる言語であれば
// ECMAScript
const ditto = (x)=>x
と書けるのとそこが大いに異なります。Type<T>
がOKでProtocol<T>
がNGなのもそれ。その悩みを解決したのがassociatedtype
で、Protocolでは
protocol Sequence {
associatedtype Element
}
としてElement
という型が内包されていることを強制した上で、
struct Array<Element> : Sequence {
//...
}
とすることで確実に型が静的に決まるようにしているわけです。さらに今では
// Element が Numeric の場合のみ.sum()できるように
extension Array where Element:Numeric {
func sum()->Element {
return self.reduce(0, +)
}
}
というように拡張の範囲を限定できたりもします。
まとめ
というわけでまとめます。
- Swiftの定数と変数の型は100%静的。例外はストロングゼロ
- プロトコル(
protocol
)も実は型を持っているが、その型は間接的にそれに準拠(conform)した型(struct
,enum
orclass
)を具現化(instantiate)する時にだけかいま見える - 「いろいろ入る型」は
enum
を使えば実現可能(例:swift-sion) - 「なんでも入る型」として
Any
も用意されているが、それをいかに使わせないかが工夫のしどころ
このあたりの感覚は、実際に自分で型を作るとよくわかります。楽しいぞい。
Dan the Safe, Fast and Expressive
追記:Any
の正体
コメントの議論にもありますが、これはやはり特記しておくべきですね。実は「何でも入る」型であるAny
もプロトコル型です。実際明示的にAny
をプロトコルとして使うことも可能ですし…
struct Whatever:Any {} // no error
REPLでAny.self
を覗いてみて見るとはっきりとそれがわかります。
1> Any.self
$R0: Any.Protocol = Any
つまり
protocol Any {}
で、全ての型宣言の裏では…
struct S {/*…*/}
enum E {/*…*/}
class C {/*…*/}
暗黙的にAny
プロトコルに準拠している…
struct S:Any {/*…*/}
enum E:Any {/*…*/}
class C:Any {/*…*/}
というわけなのです。