Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

プロトコル指向に想う世界観 #__swift__

2,377 views

Published on

2016/03/29 の「Swift愛好会 #5」で発表したスライドです。ご覧上の注意的に、スライドのレベル感は『プロトコル拡張を理解し終えたばかりで積極的に使い始めたけれど、不可解な動きに出会い暗礁に乗り上げた人向け』です。

プロトコル拡張に行き詰った時の神頼みとして頼っていただければ幸いです。これからプロトコル拡張に挑戦しよう!という人は『おさらい』まで読んだらいったん、プロトコル拡張で好き好きに遊んでみると良いかもしれません。

Published in: Engineering
  • Be the first to comment

プロトコル指向に想う世界観 #__swift__

  1. 1. EZ-NET 熊⾕友宏 http://ez-net.jp/ 2016.03.29 集まれSwift好き!Swift愛好会 #5 プロトコル指向に想う世界観 Swift カジュアルプログラミング Swift 2.2
  2. 2. 熊谷友宏 Xcode 5 徹底解説 MOSA Xcode 5 の全機能を
 徹底的に解説した本 OSX/iOS 系の歴史深い
 有料会員制の勉強会 紙版は絶版、電子書籍は販売中 Xcode 7 でも役立つはず 法人会員も多数 @es_kumagai EZ-NET http://ez-net.jp/ 書籍 / 登壇
  3. 3. 熊谷友宏 横浜 iPhone 開発者勉強会 #yidev わいわい・ゆるく、iPhone 開発者の
 みんなで楽しく過ごすのが目的の会 【 横浜・馬車道 】 カジュアル Swift 勉強会 #cswift ゆるくみんなで Swift を語らえる場を
 作りたくて始めた会 【 横浜・青葉台 】 第23回を 2016-05-07 に開催予定 第6回を 2016-04-02 に開催 @es_kumagai EZ-NET http://ez-net.jp/ 勉強会
  4. 4. 熊谷友宏 @es_kumagai EZ-NET http://ez-net.jp/ CodePiece iOS, OS X, Apple Watch アプリ ソースコードを Twitter と Gist に同時投稿できる。 いつもの電卓 計算式も見える電卓アプリ。 watchOS 1 対応 音で再配達ゴッド 簡単操作で 再配達の申し込み。 EZ-NET IP Phone iPhone でひかり電話を使う。 自宅 LAN からの利用専用
  5. 5. CodePiece for OS X 勉強会を楽しむアプリ ソースコードを Twitter と Gist に同時投稿できる
 勉強会で知見をみんなと共有したい時とかに便利! できること #__swift__
  6. 6. プロトコル指向ってなんだろう
  7. 7. プロトコル プロトコル 拡張 型 型の拡張 ▶ プロトコルは分かる。 ▶ プロトコル拡張って? ▶ 型に実装するのと違う? ▶ 型も拡張できるけど
 何が違うの? ▶ プロトコル拡張って
 ときどき変な動きする? ▶ 既定の実装に何か違和感 ▶ プロトコル拡張って
 そもそも何? プロトコル指向ってなんだろう 使うほどに見えなくなる
  8. 8. そこで
  9. 9. スピリチュアル
  10. 10. 見えない世界からアプローチしたら 見えなかった世界が見えてくる … かも?
  11. 11. あの世とこの世をつなぐもの
  12. 12. プロトコル Protocol
  13. 13. おさらい
  14. 14. プロトコル ▶ 性質を決める ▶ 性質を表現するための概念を規定する 定義 protocol Movable { func moved(x x:Int, y:Int) -> Self func movedHorizontal(x:Int) -> Self func movedVertical(y:Int) -> Self }
  15. 15. プロトコル ▶ 概念を型で実現する … 実装 ▶ その性質を持つことが約束される 適用 struct Location : Movable { func moved(x x:Int, y:Int) -> Location { return movedHorizontal(x).movedVertical(y) } func movedHorizontal(x:Int) -> Location { return Location(x: self.x + x, y: self.y) } func movedVertical(y:Int) -> Location { return Location(x: self.x, y: self.y + y) } }
  16. 16. プロトコル ▶ 型は性質の通りに振る舞える ▶ 必ず、振る舞える 実現 var location = Location(x: 0, y: 0) location = location.movedHorizontal(10) location = location.movedVertical(20) location = location.moved(x: 4, y: 8)
  17. 17. >> 概念だけで説明できるものに注目
  18. 18. ▶ プロトコルの概念だけで説明できる ▶ 適用する型ごとに変わるものではない protocol Movable { func moved(x x:Int, y:Int) -> Self func movedHorizontal(x:Int) -> Self func movedVertical(y:Int) -> Self } プロトコル 概念だけで説明できるもの
  19. 19. プロトコル拡張
  20. 20. ▶ プロトコルの概念だけで説明する ▶ 既存の概念から新しい概念を作る … 実装? protocol Movable { func moved(x x:Int, y:Int) -> Self func movedHorizontal(x:Int) -> Self func movedVertical(y:Int) -> Self } extension Movable { func moved(x x:Int, y:Int) -> Self { return movedHorizontal(x).movedVertical(y) } } プロトコル拡張 新概念を作る
  21. 21. プロトコル拡張 ▶ 最低限の概念を型に落とし込む ▶ プロトコル拡張で規定した概念が使える 型は最低限の概念だけを実現 struct Location : Movable { func movedHorizontal(x:Int) -> Location { return Location(x: self.x + x, y: self.y) } func movedVertical(y:Int) -> Location { return Location(x: self.x, y: self.y + y) } } // 既定の実装が使える let location = Location().moved(x: 10, y: 10)
  22. 22. ジェネリック関数
  23. 23. ジェネリック関数 ▶ Movable に対応する型、みたいに指定 ▶ プロトコルで規定した性質だけで組み立てる 任意の型を受け取れる関数 func randomMove<T:Movable>(location: T, maxStep: Int) -> T { let deltaX = Int(arc4random_uniform(UInt32(maxStep))) let deltaY = Int(arc4random_uniform(UInt32(maxStep))) return location.moved(x: deltaX, y: deltaY) } // Movable に対応した Location 型を渡せる var location = Location(x: 0, y: 0) location = randomMove(location, maxStep: 100)
  24. 24. プロトコルの実例
  25. 25. CollectionType 複数の要素をまとめて扱う型の性質
  26. 26. extension Location : CollectionType { var startIndex: Int { return 0 } var endIndex: Int { return 2 } subscript (index: Int) -> Int { switch index { case 0: return x case 1: return y default: fatalError() } } } CollectionType 型に適用
  27. 27. // 要素の数を取得できる location.count // 最初の要素 (x) と最後の要素 (y) を取得できる location.first! location.last! // 要素を順次処理 (x -> y) できる for v in location { } // すべての要素 (x, y) をそれぞれ 2 倍にした配列を取得する location.map { $0 * 2 } // x, y で、小さい方の値や大きい方の値を取得できる location.minElement() location.maxElement() CollectionType 配列のように振る舞う型になる
  28. 28. プロトコルは自作できる
  29. 29. 案外 むずかしい やってみると
  30. 30. protocol PlayerType { var content: Music? { get } var canPlay: Bool { get } func play() throws } プロトコル自作の難しさ インターフェイスを規定する
  31. 31. final class MusicPlayer : PlayerType { var content: Music? var canPlay: Bool { return content != nil } func play() throws { guard canPlay else { throw PlayerError.NotReady } try AVAudioPlayer(data: content!.data).play() } } プロトコル自作の難しさ 型にプロトコルを適用する
  32. 32. let player = MusicPlayer(content: Music("SomeMusic.mp3")) if player.canPlay { try player.play() } プロトコル自作の難しさ 型にプロトコルを適用したときの動き ちゃんと動く 再生 !
  33. 33. プロトコル自作の難しさ プロトコル拡張を使うとき
  34. 34. final class MusicPlayer : PlayerType { var content: Music? var canPlay: Bool { return content != nil } func play() throws { guard canPlay else { throw PlayerError.NotReady } try AVAudioPlayer(data: content!.data).play() } } プロトコル自作の難しさ プロトコルの概念だけで作られた部分 型に依存しない
  35. 35. extension PlayerType { var canPlay: Bool { return content != nil } } final class MusicPlayer : PlayerType { var content: Music? func play() throws { guard canPlay else { throw PlayerError.NotReady } try AVAudioPlayer(data: content!.data).play() } } プロトコル自作の難しさ プロトコル拡張で規定する プロトコル拡張 に移行
  36. 36. final class MusicPlayer : PlayerType { var content: Music? func play() throws { guard canPlay else { throw PlayerError.NotReady } try AVAudioPlayer(data: content!.data).play() } } プロトコル自作の難しさ プロトコルの概念だけで作られた部分 型に依存しない
  37. 37. extension PlayerType { var canPlay: Bool { return content != nil } func play() throws { guard canPlay else { throw PlayerError.NotReady } try AVAudioPlayer(data: content!.data).play() } } final class MusicPlayer : PlayerType { var content: Music? } プロトコル自作の難しさ プロトコル拡張で規定する プロトコル拡張 に移行
  38. 38. let player = MusicPlayer(content: Music("SomeMusic.mp3")) if player.canPlay { try player.play() } プロトコル自作の難しさ プロトコル拡張で規定したときの動き ちゃんと動く 再生 !
  39. 39. プロトコル自作の難しさ 拡張とは別に独自実装するとき
  40. 40. プロトコル自作の難しさ 別の型では独自に実装する final class SilentSoundPlayer : PlayerType { let content: Music? = nil var canPlay: Bool { return true } func play() throws { guard canPlay else { throw PlayerError.NotReady } try AVAudioPlayer(data: NullSound().data).play() } }
  41. 41. let player1 = MusicPlayer(content: Music("SomeMusic.mp3")) let player2 = SilentSoundPlayer() if player1.canPlay { try player1.play() } if player2.canPlay { try player2.play() } プロトコル自作の難しさ 別の型では独自に実装したときの動き それぞれ期待通りに動く 再生 ! 再生 !
  42. 42. // どんな PlayerType にも対応した再生関数を用意 func start<T:PlayerType>(player: T) throws { if player.canPlay { try player.play() } } // 用意した関数で再生する let player1 = MusicPlayer(content: Music("SomeMusic.mp3")) let player2 = SilentSoundPlayer() try start(player1) try start(player2) プロトコル自作の難しさ 別の型では独自に実装したときの動き ジェネリック関数で共通化してみる どちらも 再生 !
  43. 43. プロトコル自作の難しさ 宣言しないで拡張できる…けれど
  44. 44. protocol PlayerType { var content: Music? { get } // var canPlay: Bool { get } func play() throws } extension PlayerType { var canPlay: Bool { return content != nil } func play() throws { guard canPlay else { throw PlayerError.NotReady } try AVAudioPlayer(data: content!.data).play() } } プロトコルから 宣言を消去
  45. 45. if player1.canPlay { try player1.play() } if player2.canPlay { try player2.play() } プロトコル自作の難しさ canPlay を宣言から省いたときの動き 再生 ! こちらは動きがおかしくなる…? try start(player1) try start(player2) こちらはちゃんと動く 再生 ! player1 は 再生される player2 は 再生されない
  46. 46. プロトコル自作の難しさ さらにプロトコル拡張で共通化を図る…
  47. 47. protocol PlayerType { var content: Music? { get } // var canPlay: Bool { get } func play() throws } extension PlayerType { var canPlay: Bool { return content != nil } func play() throws { guard canPlay else { throw PlayerError.NotReady } let data = content?.data ?? NullSound().data try AVAudioPlayer(data: data).play() } } content = nil 時の 無音再生に対応
  48. 48. final class SilentSoundPlayer : PlayerType { let content: Music? = nil var canPlay: Bool { return true } // func play() throws { // // guard canPlay else { // throw PlayerError.NotReady // } // // try AVAudioPlayer(data: NullSound().data).play() // } } プロトコル拡張で 対応したので消去
  49. 49. こちらも動きがおかしくなる…? if player1.canPlay { try player1.play() } if player2.canPlay { try player2.play() } プロトコル自作の難しさ play をプロトコル拡張で共通化したときの動き 再生 ! こちらの動きは従前通りのおかしさ try start(player1) try start(player2) player1 は 再生される player2 は 再生されない canPlay は 成功 play 実行時に NotReady エラー
  50. 50. いったい何が起こっているのか
  51. 51. 見えない世界を垣間見たい 何か得体の知れない世界
  52. 52. 得てして人が頼るもの そんなときに
  53. 53. スピリチュアル
  54. 54. Swift の世界を観察 スピリチュアルで
  55. 55. プロトコル型 精神世界物質世界 〓 人間界 〓 霊界 型に囚われない世界型の世界
  56. 56. 物質世界 精神世界 人間界 霊界 明確に分離 見える 見えない 人間 (インスタンス) プロトコル型
  57. 57. 物質世界 精神世界 人間界 霊界 干渉する 人間 (インスタンス) プロトコル型 魂 / 霊 (???) 霊媒
  58. 58. Implementation を観察
  59. 59. Implementation を観察 型とプロトコルにおける実装 人間界 霊界 プロトコル型 Implementation Default Implementation extension Human { func speak() { … } } extension Speakable { func speak() { … } }
  60. 60. Implementation を観察 何に実装されるのかに着目 人間界 霊界 物質世界 精神世界 Implementation Default Implementation func speak() func speak() プロトコル型 “物体にしゃべり方を定義” … わかる気がする “魂にしゃべり方を定義” … よくわからない
  61. 61. Implementation を観察 観測的な視点に着目 人間界 霊界 物質世界 精神世界 func speak() func speak() 見える 見えない 人間 (インスタンス)
  62. 62. 見えてくること
  63. 63. プロトコル型 Implementation を観察 ふたつの“同じ”実装の違い 人間界 霊界 func speak() 実体
 ✓ 見える ✓ しゃべり方を知っているから しゃべれる
  64. 64. プロトコル型 Implementation を観察 ふたつの“同じ”実装の違い 人間界 霊界 func speak() 実体
 ✓ 見えない ✓ なんかわからないけど しゃべれる
  65. 65. なんかわからないけど 出来る
  66. 66. プロトコルは 本能
  67. 67. Implementation 人間界 霊界 プロトコル型 Implementation Default Implementation func speak() func speak() “物体にしゃべり方を搭載” … 後天的にできることを規定 “魂にしゃべり方を刻む”
 … 先天的にできることを規定 ふたつの“同じ”実装の 意味合い
  68. 68. 2世界からのアプローチ
  69. 69. 2世界からのアプローチ プロトコル拡張とは別に独自実装 ▶ プロトコル拡張でメソッドを定義 ▶ 同じメソッドを型で独自に定義 // 型の定義 class Human:Speakable { func speak() { … } } // プロトコルの定義 protocol Speakable { } extension Speakable { func speak() { … } }
  70. 70. プロトコル型 2世界からのアプローチ プロトコル拡張とは別に独自実装 人間界 霊界 func speak() func speak() 実体
 こちらが実行される こちらは実行されない 型の実装で上書きされた?
  71. 71. 2世界からのアプローチ それぞれの世界に存在する 人間界 霊界 物質世界 精神世界 人間 (インスタンス) func speak() func speak() 現実に存在している 見えないだけで存在している 見えるものを実行 魂 / 霊 (???) 本能を発現 本能は隠蔽される (言葉こそ意思疎通手段) 本能が解放される (言葉に依らない意思疎通)
  72. 72. 精神世界からの干渉
  73. 73. 歩く
  74. 74. 歩く ▶ 足を使って移動する行為 ▶ 片足を地面について重心を乗せ、
 もう片足を進行方向へ運ぶ 概要
  75. 75. 地面はどこにあるのか
  76. 76. 歩く ▶ 地面は物質世界にある ▶ 歩く概念は精神世界にも存在できる ▶ 足を地につけられるのは現実世界だけ ▶ 精神世界だけでは実現できない 地面はどこにあるのか 精神世界が現実世界に干渉する必要性
  77. 77. 歩く 人間 (インスタンス) 魂 / 霊 (???) 霊媒 精神世界が現実世界に干渉 人間界 霊界 干渉する 物質世界 精神世界 プロトコル型 歩きを発現 歩行を念ずる
  78. 78. 干渉する事柄をプロトコルで規定
  79. 79. 歩く 干渉する事柄をプロトコルで規定 ▶ プロトコルで物質世界への干渉を宣言 ▶ 型で物質世界での動きを具現化 // 型の定義 class Human:Walkable { func walk() { … } } // プロトコルの定義 protocol Walkable { func walk() } extension Walkable { } 宣言 具現化
  80. 80. 歩く 2世界からのアプローチ 人間界 霊界 物質世界 精神世界 人間 (インスタンス) func walk() func walk() 実際の歩行を定義 概念のみを規定 見えるものを実行 魂 / 霊 (???) 歩行を念じる 現実に
 干渉 現実で
 体現
  81. 81. 先天性と後天性
  82. 82. 先天性と後天性 要約 ▶ プロトコル拡張で規定したものは 先天的 ▶ 型で規定したものは 後天的 // 人間には独自の遊泳を定義 class Human:Swimmable { func swim() { … } } // 犬の遊泳は本能に従う class Dog:Swimmable { } // プロトコルの定義 protocol Swimmable { func swim() } extension Swimmable { func swim() { … } }
  83. 83. 先天性と後天性 本能の発現 人間界 霊界 物質世界 精神世界 犬 (インスタンス) func swim() { … } なぜか泳げる 魂 / 霊 (???) func swim() 現実に
 干渉 現実で
 体現 遊泳を念じる 本能が発現
  84. 84. 先天性と後天性 体験による本能の置き換え 人間界 霊界 物質世界 精神世界 人間 (インスタンス) func swim() { … } 泳ぎは習得した 魂 / 霊 (???) func swim() 遊泳を念じる 本能は隠蔽 func swim() 現実に
 干渉 現実で
 体現
  85. 85. 型からの解放
  86. 86. プロトコル型 型からの解放 魂とは何者なのか 人間界 霊界 人間 (インスタンス) func speak() func speak() 見えるものを実行 魂 / 霊 (???) 本能を発現 これは 何者なのか
  87. 87. 型からの解放 ▶ プロトコルで引数の種類を特定する ▶ 実際の型に囚われない ヒントは ジェネリック関数 func randomMove<T:Movable>(location: T, maxStep: Int) -> T { … } // Movable に対応した Location 型を渡せる var location = Location(x: 0, y: 0) location = randomMove(location, maxStep: 100)
  88. 88. 型に囚われない世界 精神世界
  89. 89. 型からの解放 ジェネリックによる昇華 人間界 霊界 物質世界 精神世界 人間 (インスタンス) func speak() func speak() ジェネリックを通して
 霊体へと昇華 魂 / 霊 (???) 本能を発現
  90. 90. 型からの解放 本能による昇華 人間界 霊界 物質世界 精神世界 人間 (インスタンス) func speak() func speak() 本能を通して魂に昇華 魂 / 霊 (???) 本能を発現 func laugh() なぜか笑える
  91. 91. 見えない世界を垣間見たい プロトコルの世界観 1. プロトコルは本能 2. 物質世界と精神世界、2つのアプローチ 3. 精神世界から物質世界への干渉を宣言 4. 先天性と後天性という実装観点 5. ジェネリックによる型からの解放
  92. 92. これが … プロトコルの世界 … !?
  93. 93. 動作事例を解く
  94. 94. プロトコル自作の難しさ canPlay を宣言から省いたときの動き
  95. 95. プロトコル自作の難しさ canPlay を宣言から省いたときの動き // このうちの player2 で不思議な動作が起こる let player1 = MusicPlayer(content: Music("SomeMusic.mp3")) let player2 = SilentSoundPlayer() // どんな PlayerType にも対応した再生関数を用意 func start<T:PlayerType>(player: T) throws { if player.canPlay { try player.play() } } ジェネリック引数は 型に囚われない 精神世界に解き放たれる 〓
  96. 96. protocol PlayerType { var content: Music? { get } // var canPlay: Bool { get } func play() throws } extension PlayerType { var canPlay: Bool { return content != nil } func play() throws { guard canPlay else { throw PlayerError.NotReady } try AVAudioPlayer(data: content!.data).play() } } プロトコルから 宣言を消去 物質世界に干渉しない 〓
  97. 97. if player1.canPlay { try player1.play() } if player2.canPlay { try player2.play() } プロトコル自作の難しさ canPlay を宣言から省いたときの動き 再生 ! こちらは動きがおかしくなる…? try start(player1) try start(player2) こちらはちゃんと動く 再生 ! player1 は 再生される player2 は 再生されない
  98. 98. プロトコル自作の難しさ canPlay を宣言から省いたときの動き 人間界 霊界 SilentSoundPlayer (インスタンス) 現実に存在している 見えないだけで存在している canPlay を発動 魂 / 霊 (ジェネリック) ジェネリック引数に渡す (型から解き放たれる) 本能が解放される (型に囚われない世界) 発現しない プロトコル型 var canPlay() var canPlay 本能が発現
  99. 99. プロトコル自作の難しさ さらにプロトコル拡張で共通化を図る…
  100. 100. protocol PlayerType { var content: Music? { get } // var canPlay: Bool { get } func play() throws } extension PlayerType { var canPlay: Bool { return content != nil } func play() throws { guard canPlay else { throw PlayerError.NotReady } let data = content?.data ?? NullSound().data try AVAudioPlayer(data: data).play() } } content = nil 時の 無音再生に対応 本能側を調整 〓 プロトコルに 宣言しない 物質世界に干渉しない 〓
  101. 101. final class SilentSoundPlayer : PlayerType { let content: Music? = nil var canPlay: Bool { return true } // func play() throws { // // guard canPlay else { // throw PlayerError.NotReady // } // // try AVAudioPlayer(data: NullSound().data).play() // } } プロトコル拡張で 対応したので消去 本能に委ねる 〓
  102. 102. こちらも動きがおかしくなる…? if player1.canPlay { try player1.play() } if player2.canPlay { try player2.play() } プロトコル自作の難しさ play をプロトコル拡張で共通化したときの動き 再生 ! こちらの動きは従前通りのおかしさ try start(player1) try start(player2) player1 は 再生される player2 は 再生されない canPlay は 成功 play 実行時に NotReady エラー
  103. 103. if player2.canPlay { プロトコル自作の難しさ play をプロトコル拡張で共通化したときの動き まずはこれから観察
  104. 104. プロトコル自作の難しさ play をプロトコル拡張で共通化したときの動き 人間界 霊界 SilentSoundPlayer (インスタンス) 現実に存在している 見えないだけで存在している 発現しない プロトコル型 var canPlay() var canPlay 実装が発現 型から直接実行 見えるものを実行
  105. 105. try player2.play() プロトコル自作の難しさ play をプロトコル拡張で共通化したときの動き そしてこれを観察
  106. 106. プロトコル自作の難しさ play をプロトコル拡張で共通化したときの動き 人間界 霊界 SilentSoundPlayer (インスタンス) 見えないだけで存在している 本能が解放される (型に囚われない世界) 発現しない プロトコル型 var canPlay() var canPlay 本能が発現 型から直接実行 なぜかplayできる var play canPlay を発動
  107. 107. プロトコルの挙動を説明できた! スピリチュアルで
  108. 108. ほかにも
  109. 109. プロトコル型 ほかにも データの保存場所は物質世界 人間界 霊界 人間 (インスタンス) var story:String データの入れ物は
 物質世界に所属 魂 / 霊 (???) 読み書きするには 現実への干渉が不可欠 var story:String
  110. 110. プロトコル型 ほかにも インスタンスは物質世界に生成 人間界 霊界 インスタンス (インスタンス) init() インスタンスは
 物質世界に生成 魂 / 霊 (???) インスタンス生成には 現実への干渉が不可欠 init()
  111. 111. 以上 プロトコル指向に想う世界観
  112. 112. まとめ プロトコル指向に想う世界観 1. プロトコル志向ってなんだろう ✓ プロトコル、ジェネリック関数、プロトコル拡張 ✓ 自作は案外難しい 2. 見えない世界を垣間見たい ✓ 2世界からのアプローチ ✓ 精神世界からの干渉 • 歩く、地面はどこにあるのか • 干渉する事柄をプロトコルで規定 ✓ 先天性と後天性 ✓ 型からの解放 3. 動作事例を解く

×