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

1.4K Views

March 29, 16

スライド概要

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

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

※ Docswell での公開に移行する直前の Slideshare での閲覧数は 3,387 でした。

profile-image

正統派趣味人プログラマー。プログラミングとは幼馴染です。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

Swift カジュアルプログラミング プロトコル指向に想う世界観 2016.03.29 集まれSwift好き!Swift愛好会 #5 EZ-NET 熊⾕友宏 Swift 2.2 http://ez-net.jp/

2.

熊谷友宏 @es̲kumagai EZ-NET http://ez-net.jp/ 書籍 / 登壇 Xcode 5 徹底解説 MOSA Xcode 5 の全機能を 徹底的に解説した本 OSX/iOS 系の歴史深い 有料会員制の勉強会 Xcode 7 でも役立つはず 法人会員も多数 紙版は絶版、電子書籍は販売中

3.

熊谷友宏 @es̲kumagai EZ-NET http://ez-net.jp/ 勉強会 横浜 iPhone 開発者勉強会 カジュアル Swift 勉強会 【 横浜・馬車道 】 #yidev 【 横浜・青葉台 】 #cswift わいわい・ゆるく、iPhone 開発者の みんなで楽しく過ごすのが目的の会 ゆるくみんなで Swift を語らえる場を 作りたくて始めた会 第23回を 2016-05-07 に開催予定 第6回を 2016-04-02 に開催

4.

熊谷友宏 @es̲kumagai EZ-NET http://ez-net.jp/ iOS, OS X, Apple Watch アプリ CodePiece いつもの電卓 ソースコードを Twitter と Gist に同時投稿できる。 計算式も見える電卓アプリ。 watchOS 1 対応 音で再配達ゴッド EZ-NET IP Phone 簡単操作で 再配達の申し込み。 iPhone でひかり電話を使う。 自宅 LAN からの利用専用

5.

CodePiece for OS X 勉強会を楽しむアプリ ソースコードを Twitter と Gist に同時投稿できる 勉強会で知見をみんなと共有したい時とかに便利! できること #̲̲swift̲̲

6.

プロトコル指向ってなんだろう

7.

プロトコル指向ってなんだろう 使うほどに見えなくなる 型 プロトコル ▶ プロトコルは分かる。 ▶ プロトコル拡張って? ▶ 型に実装するのと違う? ▶ 型も拡張できるけど 何が違うの? ▶ プロトコル拡張って ときどき変な動きする? ▶ 既定の実装に何か違和感 ▶ プロトコル拡張って そもそも何? 型の拡張 プロトコル 拡張

8.

そこで

9.

スピリチュアル

10.

見えない世界からアプローチしたら 見えなかった世界が見えてくる … かも?

11.

あの世とこの世をつなぐもの

12.

プロトコル Protocol

13.

おさらい

14.

プロトコル 定義 ▶ 性質を決める ▶ 性質を表現するための概念を規定する protocol Movable { } func moved(x x: Int, y: Int) -> Self func movedHorizontal(x: Int) -> Self func movedVertical(y: Int) -> Self

15.
[beta]
プロトコル
適用
▶ 概念を型で実現する … 実装
▶ その性質を持つことが約束される

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.

プロトコル 実現 ▶ 型は性質の通りに振る舞える ▶ 必ず、振る舞える var location = Location(x: 0, y: 0) location = location.movedHorizontal(10) location = location.movedVertical(20) location = location.moved(x: 4, y: 8)

17.

>> 概念だけで説明できるものに注目

18.

プロトコル 概念だけで説明できるもの ▶ プロトコルの概念だけで説明できる ▶ 適用する型ごとに変わるものではない protocol Movable { func moved(x x: Int, y: Int) -> Self func movedHorizontal(x: Int) -> Self func movedVertical(y: Int) -> Self }

19.

プロトコル拡張

20.
[beta]
プロトコル拡張
新概念を作る
▶ プロトコルの概念だけで説明する
▶ 既存の概念から新しい概念を作る … 実装?

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.
[beta]
プロトコル拡張
型は最低限の概念だけを実現
▶ 最低限の概念を型に落とし込む
▶ プロトコル拡張で規定した概念が使える

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.

ジェネリック関数

23.
[beta]
ジェネリック関数
任意の型を受け取れる関数
▶ 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.

プロトコルの実例

25.

CollectionType 複数の要素をまとめて扱う型の性質

26.

CollectionType 型に適用 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() } } }

27.
[beta]
CollectionType
配列のように振る舞う型になる
// 要素の数を取得できる

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()

28.

プロトコルは自作できる

29.

やってみると 案外 むずかしい

30.

プロトコル自作の難しさ インターフェイスを規定する protocol PlayerType { var content: Music? { get } var canPlay: Bool { get } func play() throws }

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.

プロトコル自作の難しさ 型にプロトコルを適用したときの動き ちゃんと動く let player = MusicPlayer(content: Music("SomeMusic.mp3")) if player.canPlay { try player.play() } 再生 !

33.

プロトコル自作の難しさ プロトコル拡張を使うとき

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.

プロトコル自作の難しさ プロトコル拡張で規定する 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.

プロトコル自作の難しさ プロトコルの概念だけで作られた部分 final class MusicPlayer : PlayerType { var content: Music? 型に依存しない func play() throws { guard canPlay else { throw PlayerError.NotReady } try AVAudioPlayer(data: content!.data).play() } }

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.

プロトコル自作の難しさ プロトコル拡張で規定したときの動き ちゃんと動く let player = MusicPlayer(content: Music("SomeMusic.mp3")) if player.canPlay { try player.play() } 再生 !

39.

プロトコル自作の難しさ 拡張とは別に独自実装するとき

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.

プロトコル自作の難しさ 別の型では独自に実装したときの動き それぞれ期待通りに動く let player1 = MusicPlayer(content: Music("SomeMusic.mp3")) let player2 = SilentSoundPlayer() if player1.canPlay { } try player1.play() 再生 ! if player2.canPlay { } try player2.play() 再生 !

42.
[beta]
プロトコル自作の難しさ
別の型では独自に実装したときの動き
ジェネリック関数で共通化してみる

// どんな 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.

プロトコル自作の難しさ 宣言しないで拡張できる…けれど

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.

プロトコル自作の難しさ canPlay を宣言から省いたときの動き こちらはちゃんと動く if player1.canPlay { } try player1.play() if player2.canPlay { } try player2.play() 再生 ! 再生 ! こちらは動きがおかしくなる…? try start(player1) try start(player2) player1 は 再生される player2 は 再生されない

46.

プロトコル自作の難しさ さらにプロトコル拡張で共通化を図る…

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 } content = nil 時の 無音再生に対応 let data = content?.data ?? NullSound().data try AVAudioPlayer(data: data).play() } }

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.

プロトコル自作の難しさ play をプロトコル拡張で共通化したときの動き こちらも動きがおかしくなる…? if player1.canPlay { } try player1.play() if player2.canPlay { } try player2.play() 再生 ! canPlay は 成功 play 実行時に NotReady エラー こちらの動きは従前通りのおかしさ try start(player1) try start(player2) player1 は 再生される player2 は 再生されない

50.

いったい何が起こっているのか

51.

見えない世界を垣間見たい 何か得体の知れない世界

52.

そんなときに 得てして人が頼るもの

53.

スピリチュアル

54.

スピリチュアルで Swift の世界を観察

55.

プロトコル 型 〓 精神世界 〓 物質世界 人間界 霊界 型の世界 型に囚われない世界

56.

人間界 霊界 物質世界 明確に分離 精神世界 見えない 見える 人間 (インスタンス) 型 プロトコル

57.

人間界 霊界 物質世界 精神世界 干渉する 魂/霊 霊媒 (???) 人間 (インスタンス) 型 プロトコル

58.

Implementation を観察

59.

Implementation を観察 型とプロトコルにおける実装 人間界 霊界 型 プロトコル Implementation extension Human { } func speak() { … } Default Implementation extension Speakable { } func speak() { … }

60.

Implementation を観察 何に実装されるのかに着目 人間界 霊界 物質世界 精神世界 型 Implementation プロトコル Default Implementation func speak() func speak() “物体にしゃべり方を定義” “魂にしゃべり方を定義” … わかる気がする … よくわからない

61.

Implementation を観察 観測的な視点に着目 人間界 霊界 物質世界 精神世界 func speak() 見える 人間 (インスタンス) func speak() 見えない

62.

見えてくること

63.

Implementation を観察 ふたつの “同じ” 実装の 違い 人間界 霊界 型 プロトコル func speak() 実体 ✓ 見える ✓ しゃべり方を知っているから しゃべれる

64.

Implementation を観察 ふたつの “同じ” 実装の 違い 人間界 霊界 型 プロトコル func speak() 実体 ✓ 見えない ✓ なんかわからないけど しゃべれる

65.

なんかわからないけど 出来る

66.

プロトコルは 本能

67.

Implementation ふたつの “同じ” 実装の 意味合い 人間界 霊界 型 Implementation プロトコル Default Implementation func speak() func speak() “物体にしゃべり方を搭載” “魂にしゃべり方を刻む” … 後天的にできることを規定 … 先天的にできることを規定

68.

2世界からのアプローチ

69.

2世界からのアプローチ プロトコル拡張とは別に独自実装 ▶ プロトコル拡張でメソッドを定義 ▶ 同じメソッドを型で独自に定義 // 型の定義 // プロトコルの定義 class Human : Speakable { protocol Speakable { func speak() { … } } extension Speakable { } func speak() { … } }

70.

2世界からのアプローチ プロトコル拡張とは別に独自実装 人間界 霊界 型 プロトコル func speak() こちらが実行される 実体 func speak() こちらは実行されない 型の実装で上書きされた?

71.

2世界からのアプローチ それぞれの世界に存在する 人間界 霊界 物質世界 精神世界 func speak() 現実に存在している func speak() 見えないだけで存在している 本能を発現 見えるものを実行 人間 本能は隠蔽される 魂/霊 本能が解放される (インスタンス) (言葉こそ意思疎通手段) (???) (言葉に依らない意思疎通)

72.

精神世界からの干渉

73.

歩く

74.

歩く 概要 ▶ 足を使って移動する行為 ▶ 片足を地面について重心を乗せ、 もう片足を進行方向へ運ぶ

75.

地面はどこにあるのか

76.

歩く 地面はどこにあるのか ▶ 地面は物質世界にある ▶ 歩く概念は精神世界にも存在できる ▶ 足を地につけられるのは現実世界だけ ▶ 精神世界だけでは実現できない 精神世界が現実世界に干渉する必要性

77.

歩く 精神世界が現実世界に干渉 人間界 霊界 干渉する 物質世界 霊媒 精神世界 型 プロトコル 歩行を念ずる 魂/霊 人間 (インスタンス) 歩きを発現 (???)

78.

干渉する事柄をプロトコルで規定

79.

歩く 干渉する事柄をプロトコルで規定 ▶ プロトコルで物質世界への干渉を宣言 ▶ 型で物質世界での動きを具現化 // 型の定義 // プロトコルの定義 class Human : Walkable { protocol Walkable { func walk() { … } func walk() } extension Walkable { } 具現化 } 宣言

80.

歩く 2世界からのアプローチ 人間界 霊界 現実で 体現 物質世界 精神世界 func walk() 実際の歩行を定義 現実に 干渉 func walk() 概念のみを規定 歩行を念じる 見えるものを実行 人間 魂/霊 (インスタンス) (???)

81.

先天性と後天性

82.

先天性と後天性 要約 ▶ プロトコル拡張で規定したものは 先天的 ▶ 型で規定したものは 後天的 // 人間には独自の遊泳を定義 // プロトコルの定義 class Human : Swimmable { protocol Swimmable { func swim() { … } func swim() } } extension Swimmable { func swim() { … } // 犬の遊泳は本能に従う class Dog : Swimmable { } }

83.

先天性と後天性 本能の発現 人間界 霊界 物質世界 現実に 干渉 精神世界 現実で 体現 func swim() func swim() { … } 本能が発現 なぜか泳げる 遊泳を念じる 犬 魂/霊 (インスタンス) (???)

84.

先天性と後天性 体験による本能の置き換え 人間界 霊界 物質世界 現実で 体現 func swim() 現実に 干渉 精神世界 func swim() func swim() { … } 本能は隠蔽 泳ぎは習得した 遊泳を念じる 人間 魂/霊 (インスタンス) (???)

85.

型からの解放

86.

型からの解放 魂とは何者なのか 人間界 霊界 型 プロトコル func speak() これは 何者なのか func speak() 本能を発現 見えるものを実行 人間 魂/霊 (インスタンス) (???)

87.
[beta]
型からの解放
ヒントは ジェネリック関数
▶ プロトコルで引数の種類を特定する
▶ 実際の型に囚われない

func randomMove<T:Movable>(location: T, maxStep: Int) -> T {
}

…

// Movable に対応した Location 型を渡せる

var location = Location(x: 0, y: 0)
location = randomMove(location, maxStep: 100)

88.

型に囚われない世界 精神世界

89.

型からの解放 ジェネリックによる昇華 人間界 霊界 物質世界 精神世界 func speak() func speak() ジェネリックを通して 霊体へと昇華 本能を発現 人間 魂/霊 (インスタンス) (???)

90.

型からの解放 本能による昇華 人間界 霊界 func speak() 物質世界 func speak() 精神世界 本能を発現 func laugh() なぜか笑える 本能を通して魂に昇華 人間 魂/霊 (インスタンス) (???)

91.

プロトコルの世界観 見えない世界を垣間見たい 1. プロトコルは本能 2. 物質世界と精神世界、2つのアプローチ 3. 精神世界から物質世界への干渉を宣言 4. 先天性と後天性という実装観点 5. ジェネリックによる型からの解放

92.

これが … プロトコルの世界 … !?

93.

動作事例を解く

94.

プロトコル自作の難しさ canPlay を宣言から省いたときの動き

95.
[beta]
プロトコル自作の難しさ
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.

protocol PlayerType { // var content: Music? { get } var canPlay: Bool { get } func play() throws プロトコルから 宣言を消去 } var canPlay: Bool { return content != nil } 〓 extension PlayerType { 物質世界に干渉しない func play() throws { guard canPlay else { throw PlayerError.NotReady } } } try AVAudioPlayer(data: content!.data).play()

97.

プロトコル自作の難しさ canPlay を宣言から省いたときの動き こちらはちゃんと動く if player1.canPlay { } try player1.play() if player2.canPlay { } try player2.play() 再生 ! 再生 ! こちらは動きがおかしくなる…? try start(player1) try start(player2) player1 は 再生される player2 は 再生されない

98.

プロトコル自作の難しさ canPlay を宣言から省いたときの動き 人間界 霊界 型 プロトコル var canPlay() 現実に存在している 発現しない 本能が発現 var canPlay 見えないだけで存在している ジェネリック引数に渡す canPlay を発動 (型から解き放たれる) SilentSoundPlayer 魂/霊 本能が解放される (インスタンス) (ジェネリック) (型に囚われない世界)

99.

プロトコル自作の難しさ さらにプロトコル拡張で共通化を図る…

100.

protocol PlayerType { 〓 func play() throws 物質世界に干渉しない } extension PlayerType { var canPlay: Bool { return content != nil } func play() throws { guard canPlay else { throw PlayerError.NotReady } 本能側を調整 〓 // var content: Music? { get } var canPlay: Bool { get } プロトコルに 宣言しない content = nil 時の 無音再生に対応 let data = content?.data ?? NullSound().data try AVAudioPlayer(data: data).play() } }

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.

プロトコル自作の難しさ play をプロトコル拡張で共通化したときの動き こちらも動きがおかしくなる…? if player1.canPlay { } try player1.play() if player2.canPlay { } try player2.play() 再生 ! canPlay は 成功 play 実行時に NotReady エラー こちらの動きは従前通りのおかしさ try start(player1) try start(player2) player1 は 再生される player2 は 再生されない

103.

プロトコル自作の難しさ canPlay を宣言から省いたときの動き まずはこれから観察 if player2.canPlay {

104.

プロトコル自作の難しさ play をプロトコル拡張で共通化したときの動き 人間界 霊界 実装が発現 型 var canPlay() 現実に存在している 見えるものを実行 型から直接実行 SilentSoundPlayer (インスタンス) プロトコル 発現しない var canPlay 見えないだけで存在している

105.

プロトコル自作の難しさ canPlay を宣言から省いたときの動き そしてこれを観察 try player2.play()

106.

プロトコル自作の難しさ play をプロトコル拡張で共通化したときの動き 発現しない 人間界 霊界 var canPlay() 型 本能が発現 var canPlay プロトコル var play canPlay を発動 見えないだけで存在している なぜか play できる 型から直接実行 SilentSoundPlayer (インスタンス) 本能が解放される (型に囚われない世界)

107.

スピリチュアルで プロトコルの挙動を説明できた!

108.

ほかにも

109.

ほかにも データの保存場所は物質世界 人間界 霊界 型 プロトコル var story:String var story:String データの入れ物は 物質世界に所属 読み書きするには 現実への干渉が不可欠 人間 魂/霊 (インスタンス) (???)

110.

ほかにも インスタンスは物質世界に生成 人間界 霊界 型 プロトコル init() init() インスタンスは 物質世界に生成 インスタンス生成には 現実への干渉が不可欠 インスタンス 魂/霊 (インスタンス) (???)

111.

以上 プロトコル指向に想う世界観

112.

プロトコル指向に想う世界観 まとめ 1. プロトコル志向ってなんだろう ✓ プロトコル、ジェネリック関数、プロトコル拡張 ✓ 自作は案外難しい 2. 見えない世界を垣間見たい ✓ 2世界からのアプローチ ✓ 精神世界からの干渉 • 歩く、地面はどこにあるのか • 干渉する事柄をプロトコルで規定 ✓ 先天性と後天性 ✓ 型からの解放 3. 動作事例を解く