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

Post on 16-Apr-2017

1.921 Views

Category:

Engineering

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

EZ-NET 熊⾕友宏 http://ez-net.jp/

2016.03.29 集まれSwift好き!Swift愛好会 #5

プロトコル指向に想う世界観Swift カジュアルプログラミング

Swift 2.2

熊谷友宏

Xcode 5 徹底解説 MOSA

Xcode 5 の全機能を徹底的に解説した本

OSX/iOS 系の歴史深い有料会員制の勉強会

紙版は絶版、電子書籍は販売中

Xcode 7 でも役立つはず 法人会員も多数

@es_kumagai EZ-NET http://ez-net.jp/

書籍 / 登壇

熊谷友宏

横浜 iPhone 開発者勉強会#yidev

わいわい・ゆるく、iPhone 開発者のみんなで楽しく過ごすのが目的の会

【 横浜・馬車道 】カジュアル Swift 勉強会

#cswift

ゆるくみんなで Swift を語らえる場を作りたくて始めた会

【 横浜・青葉台 】

第23回を 2016-05-07 に開催予定 第6回を 2016-04-02 に開催

@es_kumagai EZ-NET http://ez-net.jp/

勉強会

熊谷友宏@es_kumagai EZ-NET http://ez-net.jp/

CodePiece

iOS, OS X, Apple Watch アプリ

ソースコードを Twitter と Gist に同時投稿できる。

いつもの電卓計算式も見える電卓アプリ。 watchOS 1 対応

音で再配達ゴッド簡単操作で 再配達の申し込み。

EZ-NET IP PhoneiPhone でひかり電話を使う。 自宅 LAN からの利用専用

CodePiece for OS X勉強会を楽しむアプリ

ソースコードを Twitter と Gist に同時投稿できる 勉強会で知見をみんなと共有したい時とかに便利!

できること

#__swift__

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

プロトコル

プロトコル 拡張

型の拡張

▶ プロトコルは分かる。 ▶ プロトコル拡張って? ▶ 型に実装するのと違う? ▶ 型も拡張できるけど

何が違うの? ▶ プロトコル拡張って

ときどき変な動きする? ▶ 既定の実装に何か違和感 ▶ プロトコル拡張って

そもそも何?

プロトコル指向ってなんだろう使うほどに見えなくなる

そこで

スピリチュアル

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

… かも?

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

プロトコルProtocol

おさらい

プロトコル

▶ 性質を決める ▶ 性質を表現するための概念を規定する

定義

protocol Movable { func moved(x x:Int, y:Int) -> Self func movedHorizontal(x:Int) -> Self func movedVertical(y:Int) -> Self

}

プロトコル

▶ 概念を型で実現する … 実装 ▶ その性質を持つことが約束される

適用

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

}

プロトコル

▶ 型は性質の通りに振る舞える ▶ 必ず、振る舞える

実現

var location = Location(x: 0, y: 0)

location = location.movedHorizontal(10) location = location.movedVertical(20) location = location.moved(x: 4, y: 8)

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

▶ プロトコルの概念だけで説明できる ▶ 適用する型ごとに変わるものではない

protocol Movable {

func moved(x x:Int, y:Int) -> Self

func movedHorizontal(x:Int) -> Self func movedVertical(y:Int) -> Self

}

プロトコル概念だけで説明できるもの

プロトコル拡張

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

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

}

プロトコル拡張新概念を作る

プロトコル拡張

▶ 最低限の概念を型に落とし込む ▶ プロトコル拡張で規定した概念が使える

型は最低限の概念だけを実現

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)

ジェネリック関数

ジェネリック関数

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

プロトコルの実例

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

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

CollectionType配列のように振る舞う型になる

プロトコルは自作できる

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

protocol PlayerType {

var content: Music? { get } var canPlay: Bool { get }

func play() throws }

プロトコル自作の難しさインターフェイスを規定する

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

プロトコル自作の難しさ型にプロトコルを適用する

let player = MusicPlayer(content: Music("SomeMusic.mp3"))

if player.canPlay { try player.play() }

プロトコル自作の難しさ型にプロトコルを適用したときの動き

ちゃんと動く

再生 !

プロトコル自作の難しさ

プロトコル拡張を使うとき

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

プロトコル自作の難しさプロトコルの概念だけで作られた部分

型に依存しない

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

プロトコル自作の難しさプロトコル拡張で規定する

プロトコル拡張 に移行

final class MusicPlayer : PlayerType { var content: Music? func play() throws { guard canPlay else { throw PlayerError.NotReady } try AVAudioPlayer(data: content!.data).play() }

}

プロトコル自作の難しさプロトコルの概念だけで作られた部分

型に依存しない

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? }

プロトコル自作の難しさプロトコル拡張で規定する

プロトコル拡張 に移行

let player = MusicPlayer(content: Music("SomeMusic.mp3"))

if player.canPlay { try player.play() }

プロトコル自作の難しさプロトコル拡張で規定したときの動き

ちゃんと動く

再生 !

プロトコル自作の難しさ

拡張とは別に独自実装するとき

プロトコル自作の難しさ別の型では独自に実装する

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

let player1 = MusicPlayer(content: Music("SomeMusic.mp3")) let player2 = SilentSoundPlayer()

if player1.canPlay { try player1.play() }

if player2.canPlay { try player2.play() }

プロトコル自作の難しさ別の型では独自に実装したときの動き

それぞれ期待通りに動く

再生 !

再生 !

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

プロトコル自作の難しさ別の型では独自に実装したときの動き

ジェネリック関数で共通化してみる

どちらも 再生 !

プロトコル自作の難しさ

宣言しないで拡張できる…けれど

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

プロトコルから 宣言を消去

if player1.canPlay { try player1.play() }

if player2.canPlay { try player2.play() }

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

再生 !

こちらは動きがおかしくなる…?

try start(player1) try start(player2)

こちらはちゃんと動く

再生 !

player1 は 再生される player2 は 再生されない

プロトコル自作の難しさ

さらにプロトコル拡張で共通化を図る…

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 時の 無音再生に対応

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

プロトコル拡張で 対応したので消去

こちらも動きがおかしくなる…?

if player1.canPlay { try player1.play() }

if player2.canPlay { try player2.play() }

プロトコル自作の難しさplay をプロトコル拡張で共通化したときの動き

再生 !

こちらの動きは従前通りのおかしさ

try start(player1) try start(player2) player1 は 再生される

player2 は 再生されない

canPlay は 成功 play 実行時に NotReady エラー

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

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

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

スピリチュアル

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

プロトコル型

精神世界物質世界

人間界〓

霊界型に囚われない世界型の世界

物質世界 精神世界

人間界 霊界

明確に分離

見える 見えない

人間 (インスタンス)

プロトコル型

物質世界 精神世界

人間界 霊界

干渉する

人間 (インスタンス)

プロトコル型

魂 / 霊 (???) 霊媒

Implementation を観察

Implementation を観察型とプロトコルにおける実装

人間界 霊界

プロトコル型

Implementation Default Implementation

extension Human {

func speak() { … } }

extension Speakable {

func speak() { … } }

Implementation を観察何に実装されるのかに着目

人間界 霊界

物質世界 精神世界

Implementation Default Implementation

func speak() func speak()

プロトコル型

“物体にしゃべり方を定義” … わかる気がする

“魂にしゃべり方を定義” … よくわからない

Implementation を観察観測的な視点に着目

人間界 霊界

物質世界 精神世界

func speak() func speak()

見える 見えない

人間 (インスタンス)

見えてくること

プロトコル型

Implementation を観察ふたつの “同じ” 実装の 違い

人間界 霊界

func speak()

実体✓ 見える ✓ しゃべり方を知っているから しゃべれる

プロトコル型

Implementation を観察ふたつの “同じ” 実装の 違い

人間界 霊界

func speak()

実体✓ 見えない ✓ なんかわからないけど しゃべれる

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

プロトコルは

本能

Implementation

人間界 霊界

プロトコル型

Implementation Default Implementation

func speak() func speak()

“物体にしゃべり方を搭載” … 後天的にできることを規定

“魂にしゃべり方を刻む”… 先天的にできることを規定

ふたつの “同じ” 実装の 意味合い

2世界からのアプローチ

2世界からのアプローチプロトコル拡張とは別に独自実装

▶ プロトコル拡張でメソッドを定義 ▶ 同じメソッドを型で独自に定義

// 型の定義 class Human : Speakable {

func speak() { …

} }

// プロトコルの定義 protocol Speakable {

}

extension Speakable {

func speak() { …

} }

プロトコル型

2世界からのアプローチプロトコル拡張とは別に独自実装

人間界 霊界

func speak() func speak()

実体

こちらが実行される こちらは実行されない

型の実装で上書きされた?

2世界からのアプローチそれぞれの世界に存在する

人間界 霊界

物質世界 精神世界

人間 (インスタンス)

func speak() func speak()

現実に存在している 見えないだけで存在している

見えるものを実行

魂 / 霊 (???)

本能を発現

本能は隠蔽される (言葉こそ意思疎通手段)

本能が解放される (言葉に依らない意思疎通)

精神世界からの干渉

歩く

歩く

▶ 足を使って移動する行為

▶ 片足を地面について重心を乗せ、もう片足を進行方向へ運ぶ

概要

地面はどこにあるのか

歩く

▶ 地面は物質世界にある ▶ 歩く概念は精神世界にも存在できる ▶ 足を地につけられるのは現実世界だけ ▶ 精神世界だけでは実現できない

地面はどこにあるのか

精神世界が現実世界に干渉する必要性

歩く

人間 (インスタンス)

魂 / 霊 (???)

霊媒

精神世界が現実世界に干渉

人間界 霊界

干渉する

物質世界 精神世界

プロトコル型

歩きを発現

歩行を念ずる

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

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

▶ プロトコルで物質世界への干渉を宣言 ▶ 型で物質世界での動きを具現化

// 型の定義 class Human : Walkable {

func walk() { …

} }

// プロトコルの定義 protocol Walkable {

func walk() }

extension Walkable {

}

宣言

具現化

歩く2世界からのアプローチ

人間界 霊界

物質世界 精神世界

人間 (インスタンス)

func walk() func walk()

実際の歩行を定義 概念のみを規定

見えるものを実行

魂 / 霊 (???)

歩行を念じる

現実に干渉

現実で体現

先天性と後天性

先天性と後天性要約

▶ プロトコル拡張で規定したものは 先天的 ▶ 型で規定したものは 後天的

// 人間には独自の遊泳を定義 class Human : Swimmable {

func swim() { …

} }

// 犬の遊泳は本能に従う class Dog : Swimmable {

}

// プロトコルの定義 protocol Swimmable {

func swim() }

extension Swimmable {

func swim() { …

} }

先天性と後天性本能の発現

人間界 霊界

物質世界 精神世界

犬 (インスタンス)

func swim() { … }

なぜか泳げる

魂 / 霊 (???)

func swim()

現実に干渉

現実で体現

遊泳を念じる

本能が発現

先天性と後天性体験による本能の置き換え

人間界 霊界

物質世界 精神世界

人間 (インスタンス)

func swim() { … }

泳ぎは習得した

魂 / 霊 (???)

func swim()

遊泳を念じる

本能は隠蔽

func swim()

現実に干渉

現実で体現

型からの解放

プロトコル型

型からの解放魂とは何者なのか

人間界 霊界

人間 (インスタンス)

func speak() func speak()

見えるものを実行

魂 / 霊 (???)

本能を発現

これは 何者なのか

型からの解放

▶ プロトコルで引数の種類を特定する ▶ 実際の型に囚われない

ヒントは ジェネリック関数

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

// Movable に対応した Location 型を渡せる var location = Location(x: 0, y: 0)

location = randomMove(location, maxStep: 100)

型に囚われない世界

精神世界

型からの解放ジェネリックによる昇華

人間界 霊界

物質世界 精神世界

人間 (インスタンス)

func speak() func speak()

ジェネリックを通して霊体へと昇華

魂 / 霊 (???)

本能を発現

型からの解放本能による昇華

人間界 霊界

物質世界 精神世界

人間 (インスタンス)

func speak() func speak()

本能を通して魂に昇華

魂 / 霊 (???)

本能を発現

func laugh()

なぜか笑える

見えない世界を垣間見たいプロトコルの世界観

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

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

動作事例を解く

プロトコル自作の難しさ

canPlay を宣言から省いたときの動き

プロトコル自作の難しさ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() } }

ジェネリック引数は 型に囚われない

精神世界に解き放たれる〓

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

プロトコルから 宣言を消去

物質世界に干渉しない

if player1.canPlay { try player1.play() }

if player2.canPlay { try player2.play() }

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

再生 !

こちらは動きがおかしくなる…?

try start(player1) try start(player2)

こちらはちゃんと動く

再生 !

player1 は 再生される player2 は 再生されない

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

人間界 霊界

SilentSoundPlayer (インスタンス)

現実に存在している 見えないだけで存在している

canPlay を発動

魂 / 霊 (ジェネリック)

ジェネリック引数に渡す (型から解き放たれる)

本能が解放される (型に囚われない世界)

発現しない

プロトコル型

var canPlay() var canPlay

本能が発現

プロトコル自作の難しさ

さらにプロトコル拡張で共通化を図る…

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 時の 無音再生に対応

本能側を調整

プロトコルに 宣言しない

物質世界に干渉しない

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

プロトコル拡張で 対応したので消去

本能に委ねる

こちらも動きがおかしくなる…?

if player1.canPlay { try player1.play() }

if player2.canPlay { try player2.play() }

プロトコル自作の難しさplay をプロトコル拡張で共通化したときの動き

再生 !

こちらの動きは従前通りのおかしさ

try start(player1) try start(player2) player1 は 再生される

player2 は 再生されない

canPlay は 成功 play 実行時に NotReady エラー

if player2.canPlay {

プロトコル自作の難しさplay をプロトコル拡張で共通化したときの動き

まずはこれから観察

プロトコル自作の難しさplay をプロトコル拡張で共通化したときの動き

人間界 霊界

SilentSoundPlayer (インスタンス)

現実に存在している 見えないだけで存在している

発現しないプロトコル型

var canPlay() var canPlay

実装が発現

型から直接実行

見えるものを実行

try player2.play()

プロトコル自作の難しさplay をプロトコル拡張で共通化したときの動き

そしてこれを観察

プロトコル自作の難しさplay をプロトコル拡張で共通化したときの動き

人間界 霊界

SilentSoundPlayer (インスタンス)

見えないだけで存在している

本能が解放される (型に囚われない世界)

発現しない

プロトコル型

var canPlay() var canPlay

本能が発現

型から直接実行

なぜか play できる

var play

canPlay を発動

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

ほかにも

プロトコル型

ほかにもデータの保存場所は物質世界

人間界 霊界

人間 (インスタンス)

var story:String

データの入れ物は物質世界に所属

魂 / 霊 (???)

読み書きするには 現実への干渉が不可欠

var story:String

プロトコル型

ほかにもインスタンスは物質世界に生成

人間界 霊界

インスタンス (インスタンス)

init()

インスタンスは 物質世界に生成

魂 / 霊 (???)

インスタンス生成には 現実への干渉が不可欠

init()

以上

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

まとめプロトコル指向に想う世界観

1. プロトコル志向ってなんだろう ✓ プロトコル、ジェネリック関数、プロトコル拡張 ✓ 自作は案外難しい

2. 見えない世界を垣間見たい ✓ 2世界からのアプローチ ✓ 精神世界からの干渉

• 歩く、地面はどこにあるのか • 干渉する事柄をプロトコルで規定

✓ 先天性と後天性 ✓ 型からの解放

3. 動作事例を解く

top related