>100 Views
June 28, 15
スライド概要
2015/06/28 に @k_katsumi さん主催で開催された「Swift 2 シンポジウム」 (http://realm.connpass.com/event/16556/) で発表させて頂いた資料です。
初めて Swift 2.0 に触れたときに大域関数がごっそり削除されていた衝撃から、それを敢行する上での柱となっていた Protocol Extension の魅力を紹介し、それを以って積極的に使っても良いものなのかを議論させて頂きました。後半は疑問系が連発しますが「自分はそう思ってるけれど、どう感じますか?」という意味合いです。自分はこう思う!とかありましたらぜひぜひ教えてくださいませ。
※ Docswell での公開に移行する直前の Slideshare での閲覧数は 12,742 でした。
正統派趣味人プログラマー。プログラミングとは幼馴染です。
Swift 2.0 大域関数の行方から 2015.06.28 @ Swift 2 (& LLDB) シンポジウム EZ-‐‑‒NET 熊⾕谷友宏 http://ez-‐‑‒net.jp/
熊谷友宏 EZ-NET http://ez-net.jp/ @es̲kumagai Xcode 5 徹底解説 MOSA IP Phone 音でダイヤル いつもの電卓 with 割勘ウォッチ 音で再配達ゴッド
Swift 2.0 で 衝撃的だった出来事
大域関数の大胆な削減
Swift 1.2 • abs • advance • alignof • alignofValue • assert • assertionFailure • contains • count • debugPrint • debugPrintln • distance • dropFirst • dropLast • dump • enumerate • equal • extend • fatalError • filter • find • first • flatMap • getVaList • indices • insert • isEmpty • isUniquelyRefer enced • isUniquelyRefer encedNonObjC • join • last • lazy • lexicographicalC ompare • map • max • maxElement • min • minElement • numericCast • overlaps • partition • precondition • preconditionFail ure • prefix • print • println • reduce • reflect • removeAll • removeAtIndex • removeLast • removeRange • reverse • sizeof • sizeofValue • sort • sorted • splice • split • startsWith • stride • strideof • strideofValue • suffix • swap • toDebugString • toString • transcode • underestimateC ount • unsafeAddress Of • unsafeBitCast • unsafeDowncast • unsafeUnwrap • withExtendedLif etime • withUnsafeMuta blePointer • withUnsafeMuta blePointers • withUnsafePoint er • withUnsafePoint ers • withVaList • zip
Swift 2.0 • abs • advance • alignof • alignofValue • assert • assertionFailure • contains • count • debugPrint • debugPrintln • distance • dropFirst • dropLast • dump • enumerate • equal • extend • fatalError • filter • find • first • flatMap • getVaList • indices • insert • isEmpty • isUniquelyRefer enced • isUniquelyRefer encedNonObjC • join • last • lazy • lexicographicalC ompare • map • max • maxElement • min • minElement • numericCast • overlaps • partition • precondition • preconditionFail ure • prefix • print • println • reduce • reflect • removeAll • removeAtIndex • removeLast • removeRange • reverse • sizeof • sizeofValue • sort • sorted • splice • split • startsWith • stride • strideof • strideofValue • suffix • swap • toDebugString • toString • transcode • underestimateC ount • unsafeAddress Of • unsafeBitCast • unsafeDowncast • unsafeUnwrap • withExtendedLif etime • withUnsafeMuta blePointer • withUnsafeMuta blePointers • withUnsafePoint er • withUnsafePoint ers • withVaList • zip • anyGenerator • readLine
思ったほどは消えてない? ################################################################################# ++ -------------------------
なぜ大胆に削除されたと感じたか
Swift 2.0 削除された大域関数 • contains • count • debugPrintln • enumerate • equal • filter • find • first • flatMap • indices • isEmpty • last • lexicographicalCompare • map • maxElement • minElement • partition • println • reduce • reverse • sorted • startsWith • toDebugString • toString • underestimateCount
削除された大域関数のすべてが ジェネリック関数
おさらい
プロトコル 型の振る舞いを決めるもの
ジェネリック 型に縛られないプログラミング
Swift 1.2 プロトコル プロトコルを定義する protocol CollectionType { typealias Element typealias Index : ForwardIndexType subscript(index:Index) -> Element { get } } var startIndex:Index { get } var endIndex:Index { get } これらの機能が使えることを保証
Swift 1.2
ジェネリック関数
プロトコルを想定して機能をつくる
func count<T:CollectionType>(collection:T)
-> T.Index.Distance {
let start = collection.startIndex
let end = collection.endIndex
}
return distance(start, end)
保証された機能を使ってプログラミング
機能ができたら型をつくる
Swift 1.2 型をつくる データ構造を決める struct Month { var days:Array<Day> init(days:Int) { self.days = (1...days).map(Day.init) } }
Swift 1.2 型をつくる プロトコルに基づき振る舞いを決める extension Month : CollectionType { subscript(index:Int) -> Day { return self.days[index - 1] } var startIndex:Int { return 1 } var endIndex:Int { return self.days.count + 1 } }
機能を使う
Swift 1.2 機能を使う ジェネリック関数を使う let june = Month(days: 30) // 数えられるようになっている count(june)
Swift 1.2 機能を使う 新しく型を作ったときも使える struct Year : CollectionType { : : } let year = Year(2015) // 同じ関数をすぐに使える count(year)
振る舞いに着目して組み上げるのが ジェネリックプログラミング
ジェネリックプログラミング 得られる恩恵 ▶ 型を作るときに構造に専念できる ▶ 型に備えるべき機能がプロトコルで判る ▶ 機能を共用化できる ▶ 未知の型にも対応できる ▶ 準拠するプロトコルから出来ることが判る
大域関数の削除
そもそもなぜ 大域関数の削除が気になったのか プロトコルを活かす仕組みだったから
Swift 1.2
プロトコルと機能の実装
プロトコルを定義して
protocol CollectionType {
}
それを使った型を作る
struct Array<T> : CollectionType {
}
struct Dictionary<Key,Value> : CollectionType {
}
Swift 1.2 プロトコルと機能の実装 大域にジェネリック関数を揃えると func count<T:CollectionType>(x:T) -> T.Index.Distance func indices<C:CollectionType>(x:C) -> Range<C.Index> どちらの型にでも使える let array = Array<Int>() let dictionary = Dictionary<String,Int>() count(array) count(dictionary)
Swift 1.2 で 気になっていたこと
Swift 1.2
両方に同じ機能がある
大域関数に実装されているものが
func count<T:CollectionType>(x:T)
-> T.Index.Distance
型にも実装されていたり
struct Array<T> : CollectionType {
var count: Int { get }
}
Swift 1.2 なぜ両方にあるのか どちらも同じ機能 let c = count(array) let c = array.count ▶ 大域関数にある機能をなぜ型にも? ▶ array.count と書く方が便利だから?
Swift 1.2 大域関数だけで良いのでは…?
Swift 2.0 むしろ大域関数が消滅
大域関数の行方
Swift 2.0 汎用的な大域関数が それぞれの型に固有な実装へ 削除だけでは済まないはず
Swift 2.0 大域関数の行方 SequenceType CollectionType • contains • enumerate • filter • flatMap • lexicographicalCompare • map • maxElement • minElement • reduce • reverse • sorted • startsWith • underestimateCount • count • first • filter • isEmpty • last • indexOf (find) • indices MutableCollectionType • debugPrintln • println • partition String • init: (toString) • init:reflecting: (toDebugString) 削除
移動先の多くが Protocol Extension
Swift 2.0 Protocol Extension 特徴 ▶ プロトコルを拡張する機能 ▶ 既定の実装を記載できる ▶ プロトコルに規定された機能で実装する ▶ 型エイリアスの種類で実装を変えられる
Swift 2.0 Protocol Extension たとえば、こんなプロトコルがあったとき protocol CollectionType { typealias Element typealias Index : ForwardIndexType subscript(index:Index) -> Element { get } } var startIndex:Index { get } var endIndex:Index { get }
Swift 2.0
Protocol Extension
プロトコル拡張で振る舞いから機能を実装
extension CollectionType {
var count:Index.Distance {
}
return distance(self.startIndex, self.endIndex)
var indices:Range<Index> {
}
}
return self.startIndex ..< self.endIndex
Swift 2.0 Protocol Extension 型では最低限の機能だけを実装すれば struct Month : CollectionType { subscript(index:Int) -> Day { return self.days[index - 1] } var startIndex:Int { return 1 } var endIndex:Int { return self.days.count + 1 } }
Swift 2.0 Protocol Extension プロトコル拡張で実装した機能も使える let month = Month() month.count month.indices
つまり
Swift 1.2
// 振る舞いをプロトコルで規定
protocol CollectionType {
typealias Element
typealias Index : ForwardIndexType
subscript(index:Index) -> Element { get }
var startIndex:Index { get }
var endIndex:Index { get }
}
// 機能は大域関数で提供
func count<T:CollectionType>(x:T) -> T.Index.Distance {
return distance(x.startIndex, x.endIndex)
}
func indices<C:CollectionType>(x:C) -> Range<C.Index> {
return x.startIndex ..< x.endIndex
}
Swift 2.0
// 振る舞いも機能もプロトコル内に集約
protocol CollectionType {
typealias Element
typealias Index : ForwardIndexType
subscript(index:Index) -> Element { get }
var startIndex:Index { get }
var endIndex:Index { get }
}
extension CollectionType {
var count:Index.Distance {
return distance(self.startIndex, self.endIndex)
}
}
var indices:Range<Index> {
return self.startIndex ..< self.endIndex
}
Protocol Extension 嬉しいポイント
Protocol Extension 1. コード補完が効く let array = Array<String>() array.count ▶ 所属する機能として候補に挙がる ▶ 思考の流れどおりに書ける
Protocol Extension 2. 条件付きで拡張できる extension CollectionType where Element : IntegerType { var total:Element { return self.reduce(0, combine:+) } } ▶ 型エイリアスで条件を指定できる ▶ 条件を満たす型にだけ機能が追加される
Protocol Extension 2. 条件付きで拡張できる let intArray = Array<Int>() let strArray = Array<String>() intArray.total strArray.total ▶ 条件を満たす型でだけ使える ▶ それ以外の型にはそもそも存在しない
Protocol Extension は プロトコルを活かせる理想形
Protocol Extension に 想いを馳せる日々
役割について
〓 Protocol Extension ジェネリック関数を置き換える機能?
Protocol Extension おさらい ▶ ジェネリック関数と同等のことができる ▶ 機能がプロトコルでグループ化される ▶ 補完機能が働くのが嬉しい 大域関数が要らなくなる?
Protocol Extension ジェネリック関数にしかできないこと ▶ 名前空間による完全な衝突回避 衝突の可能性 ▶ プロトコル自体は名前空間で回避できる ▶ 要求する機能が衝突すると回避できない
衝突について
1. プロトコル名の衝突 複数のモジュールで同じ名前なら回避可能 // JSON Module protocol ValueType { // XML Module protocol ValueType { } } // モジュールを明記することで回避可能 struct JSONValue : JSON.ValueType { }
2. 機能の衝突 異なる目的を同じ定義が求めたときは衝突 protocol PNGType { protocol JPEGType { var data:NSData { get } } var data:NSData { get } } struct Picture : PNGType, JPEGType { // どちらの data を期待している? } var data:NSData { } プロトコル名での区別ができない
2. 機能の衝突 機能名を具体的なものにして衝突を回避する方法 protocol PNGType { } var pngData:NSData { get } protocol JPEGType { } var jpegData:NSData { get } struct Picture : PNGType, JPEGType { // もし名前が衝突しても同じ目的の可能性が高い } var jpegData:NSData { } 名前が長くなりすぎないようには心掛けたい
2. 機能の衝突 オーバーロードで衝突を回避する方法 protocol PNGType { } var data:PNGData { get } protocol JPEGType { } var data:JPEGData { get } struct Picture : PNGType, JPEGType { // 戻り値が違えば別物として存在できる var data:PNGData { return … } var data:JPEGData { return … } } 独自の型を使えばほぼ衝突は回避できる
3. 型エイリアスの衝突 同名の型エイリアスが異なる型を期待すると衝突 protocol ProtoA { typealias Element : protocol ProtoB { typealias Element : struct Picture : ProtoA, ProtoB { でも具体例が思いつかない…!
衝突は案外、心配なさそう
Protocol Extension は 積極的に活用して行くのが良さそう
そもそもの extension について
Objective-C の頃は カテゴリ拡張は避けられていた感 【カテゴリ拡張】 クラスに後から独自のメソッドを追加する機能
Swift だと extension を使うことに 不安を感じない気がする ▶ Objective-C はクラス継承が主体で 影響範囲が広すぎたから…? ▶ プロトコルの継承が主体で 拡張するにも動機が明確だから…?
extension の使いどころについて
Protocol Extension 使いどころの判断基準 ▶ 大域関数の代わりとして積極的に活用? ▶ 演算子の実装を除くすべて? ▶ 既存のプロトコルも積極的に拡張? 普通の書き方として使っていくべき?
Protocol Extension 拡張できないプロトコルも存在する Any や AnyObject は拡張できない ( Non-nominal type cannot be extended ) 任意の型やオブジェクトに対する 機能の実装には大域関数が必要
Protocol Extension Swift 2.0 の標準機能は大域関数も存在する func abs<T:SignedNumberType>(x:T) -> T func advance<T:ForwardIndexType> (start:T, _ n:T.Distance) -> T ▶ なぜ大域関数として残されているのか ▶ Protocol Extension だけでは問題なのか? なにか理由があるのだろうか
Protocol Extension 存在理由が分かる気がする大域関数 func stride<T:Strideable>(from start:T, through end:T, by stride:T.Stride) -> StrideThrough<T> ▶ stride は気持ちが分かる気がする ▶ Strideable を操作するのではなく 値を使って Stride 型を得ることが目的 ▶ 実際 Protocol Extension されていない
Protocol Extension 存在理由がよくわからない大域関数 func removeAll <C:RangeReplaceableCollectionType> (inout x:C, keepCapacity:Bool = default) ▶ Protocol Extension だけで十分そう ▶ 深い事情で残されているわけではない? ▶ それとも inout が何か鍵を握っている?
まとめ
Swift 2.0 大域関数の行方から ▶ 大域関数の大胆な削減 ✴ 大域関数は Protocol Extension へ ▶ Protocol Extension の気になるところ ✴ 名前の衝突は心配要らなそう? ✴ 既存プロトコルも積極的に拡張できそう? ▶ Protocol Extension の使いどころ ✴ 大域関数を置き換える機能? ✴ 大域関数は標準ライブラリに残っている ✴ 使い分けのポイントがあるのだろうか