詳解Swift#3(chapter6 パターン)
昨日に続いて、詳解Swiftの写経を続ける。
- 作者: 荻原剛志
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2015/12/25
- メディア: 単行本
- この商品を含むブログを見る
chapter6 パターン
いくつかのデータを組にして扱うことができる タプルと列挙型 についての章。
タプル
複数個のデータを組にしてまとめたもの。データは値型。
といったシーンで活躍する。
- シンプルなタプルの例
let m = ("monkey.jpg", 161_022) let m2: (String, Int) = ("monkey.jpg", 161_022) let cat: (String, Int, Int) = ("cat.jpg", 1024, 768) var img = cat //img = m2 <- error var img2 = cat print(cat.0) // cat.jpg img.1 = 1920 // img == img2 <- error let m3 = (file: "tiger.jpg", width: 1024, height: 768) m3.file m3.0
変数名: (1つ目の要素の型, 2目の要素の型, .....) = (要素1の値, 要素2の値, .....)
で記述する。タプルの要素へのアクセスは タプル変数.添え字
と書く。要素にキーワードが付いている場合は タプル変数.要素キーワード名
でもアクセスできる。要素の値を書き換えることもできる。タプルの要素の1つにタプルを入れることもできる。
タプルの代入には要素の個数と型が一致していなければいけない、タプルの比較はできないなどの制約がある。
- タプルを返す関数
func BMI(tall:Double, _ weight:Double) -> (bmi: Double, idealWeight: Double) { let ideal = 22.0 // 理想的な値 let t2 = tall * tall / 10000.0 // cm を m に換算して二乗 let index = weight / t2 // BMIを計算 return (index, ideal * t2) // 目標体重も計算して返す } var ret = BMI(172.0, 58.5) print(ret.bmi) print(ret.idealWeight)
関数の戻り値を (Double, Double)
とすることで、2つのDouble型の値を含むタプルを返却できる。これは便利。
- キーワード付きのタプル
let img = ("phoenix.png", 1200, 800) let photo = (file:"tiger.jpg", width:640, height:800) print(img) print(photo) var v1 : (String, Int, Int) = img var v2 : (file:String, width:Int, height:Int) = img var v3 : (image:String, x:Int, y:Int) = img v1 = photo v2 = photo // v3 = photo // エラーになる print("v1 = \(v1)") print("v2 = \(v2)") v3 = photo as (String, Int, Int) print("v3 = \(v3)")
キーワード付きのタプルへの代入は同じキーワードと型を持つタプルかキーワードを持たないタプルでないとできない。↑の例だと異るキーワードを持つタプルv3への代入はエラーとなる。
列挙型
Swiftでの列挙型は列挙型の中にメソッドやプロパティの定義ができたり、イニシャライザも定義できたり拡張性が高い。
- シンプルな列挙型
enum Direction { case Up case Down case Right case Left } Direction.Up print(Direction.Up, terminator: "") // Up
- メソッド付きの列挙型
enum Direction { case Up, Down, Right, Left func clockwise() -> Direction { switch self { case Up: return Right case Down: return Left case Right: return Down case Left: return Up } } mutating func changeClock() { switch self { case Up: self = Right case Down: self = Left case Right: self = Down case Left: self = Up } } } let d = Direction.Up var d2 = Direction.Up d.clockwise() print(d) // 値は変わらない //d.changeClock() <- error d2.changeClock() // ここで値を変更(Up -> Right) print(d2)
メソッド付きの列挙型。 mutating
キーワードを付けると列挙型自身の値を変更するメソッドも作れる。計算型のプロパティも定義できる。
- 列挙体のタイプメソッド、タイププロパティ
enum Direction : Int { case Up = 0, Right, Down, Left static var defaultDirection = Direction.Up init() { self = Direction.defaultDirection } static func arrow(d:Direction) -> String { return ["↑", "→", "↓", "←"][d.rawValue] } func clockwise() -> Direction { let t = (self.rawValue + 1) % 4 return Direction(rawValue:t)! } var horizontal: Bool { switch self { case Right, Left: return true default: return false } } mutating func turnBack() { self = Direction(rawValue:((self.rawValue + 2) % 4))! } } Direction.defaultDirection = .Right var e = Direction() print(Direction.arrow(e))
構造体と同様にタイプメソッド、タイププロパティは static
, イニシャライザは init
で定義できる。
共用型の列挙型
enum WebColor { case Name(String) // 色の名前 case Code(String) // 16進数によるカラーコード case White, Black, Red, Blue, Green, Yellow, Gray } let indigo = WebColor.Name("indigo") // インディゴブルー let turquoise: WebColor = .Code("#40E0D0") // ターコイズ let textColor = WebColor.Black
値型の列挙型とはまったくユースケースが異る。同じ役割のオブジェクトなんだけど、使い方(定義の仕方)が違う。みたいな場合に使えそう。
- if-case文
enum Ticket { case 切符(Int, Bool, 回数券:Bool) // 普通券:価格、小人、回数券かどうか case カード(Int, Bool) // プリペイドカード:残高、小人 case 敬老パス // 敬老パス } var list:[Ticket] = [ .切符(250, false, 回数券:false), .カード(3300, false), .敬老パス, ] for p in list { /* エラー if p == .カード { print("プリペイドカード") } */ switch p { // switch文ではこのように記述できる case .カード: print("プリペイドカード") default: break } } // if-case文を使うとよりシンプルに for p in list { if case .カード = p { print("プリペイドカード") } }
共用型の列挙型の配列から条件にあうものだけで処理したい場合に使える。残念だけど今のところ、どれくらい便利なのかがわからん。
- for-in文でもcaseパターンを使える
enum Ticket { case 切符(Int, Bool, 回数券:Bool) // 普通券:価格、小人、回数券かどうか case カード(Int, Bool) // プリペイドカード:残高、小人 case 敬老パス // 敬老パス } var passes:[Ticket] = [ .切符(320, false, 回数券:false), .切符(120, true, 回数券:true), .切符(240, false, 回数券:true), .切符(240, true, 回数券:false), .敬老パス, .カード(3300, false) ] for case let .切符(fare, child, coupon) in passes where fare > 220 { var k = coupon ? "回数券" : "普通券" if child { k += "(小人)" } print(k, "\(fare)円") }
ここまで読んで有用性が少し見えてきた気がする。列挙型の配列の中から複数条件を指定して処理を書く場合にうまく使えばシンプルに書ける。ただ、caseパターンが複数あったり、条件が複雑過ぎて横に記述が長くなるので万能ではない印象。
- 再帰的な列挙型
enum メッセージ : CustomStringConvertible { case 文書 (String, String) // 差出人、文書 case データ(String, [Int8]) // 差出人、データ列 indirect case 転送(String, メッセージ) // 差出人、メッセージ var description: String { switch self { case let 文書(from, str): return from + "(" + str + ")" case let データ(from, _): return from + "[データ]" case let 転送(from, msg): return from + "←\(msg)" } } } let m1 = メッセージ.文書("伊藤", "休みます") print(m1) // 伊藤(休みます) let m2 = メッセージ.転送("白石", m1) print(m2) // 白石←伊藤(休みます) let m3 = メッセージ.転送("山田", m2) print(m3) // 山田←白石←伊藤(休みます)```
再帰的に列挙型のタプルに列挙型自身を要素として指定する場合は indirect
キーワードをcaseパターンの前に記述する。
パターンマッチ
どの構造に何が当てはまるのかというルールを定義して、ルールに基づいたデータ処理をパターンマッチという。
- タプル
- 共用型の列挙型
で利用できる。
- ワイルドカード
_
- 変数/定数
- Switch文においての整数、区間
などでパターンを書いて、それぞれにマッチする場合の処理を記述する。
所感・雑感
- タプル
- 代入の要素/型制約などを見ても型についての厳格さを感じる。個人的には嫌いではないものの、実戦では少し窮屈に思う時があるかもしれない
- タプルの要素にはキーワードを付けるのをデフォルトにしたい。添字だけだと、コードが大きくなった場合に見通しが悪くなりそう。
- 列挙体
- 構造体と同様に確かに拡張高い。コード系の管理とか、コードに関する振る舞いを列挙体自身のメソッドにしておくと、一貫性あるコードを書けそう。
- 「共用型」という名前の意味がわからん。何を共用するのか。
- if-case / for-in でのcaseなどはライブラリのコードを読んでみたい
- パターンマッチ
- 例が少なかったので、逆引きリファレンスとかレシピがほしい
今日はあんまり時間とれなかったので1章分。夜に時間あったらもうちょい進める。