はてな教科書の「プログラミング言語 Swift」を読んでます 3
https://github.com/hatena/Hatena-Textbook/blob/master/swift-programming-language.md
のんびり読んでいきます。
Functions
guard
guardは関数の early exits をサポートする構文
- guardの条件式に書かれた条件が満たされなかったら終了したり、異常の場合の値を返したり、といったことに使いそうです
- guard (式) else { 式がfalseだった場合の処理 } ...
式がfalseだった場合の処理
では必ず、 return, break, continue, throw のいずれかまたは @noreturn 関数の呼び出しを行う必要があります
- 式の中でletで宣言した値はguardを超えたあとで利用出来ます
- guardの式でDictionaryの値の取得がfalseになる条件は取り出そうとしているキーが存在しないこと(キーの値はnilでもよい)
101> func guardTest(condition: Bool) -> String? { 102. guard condition == true else { 103. return nil 104. } 105. return "true" 106. } 107> guardTest(true) $R8: String? = "true" 108> guardTest(false) $R9: String? = nil 109>
教科書の例の下記の部分がちょっとよくわからないので、読んでいくぞ、という感じです。
guard let name = contact["name"], let mail = contact["mail"] else { return nil }
パターンマッチする場合、nilを許可しているならこの条件にfalseが入ってきて else を通るけれど、受け付けているのOptional型ではないので通るのだろうか...。
と思ったのですが、Rubyと違ってnilが入っていてもここはtrueになって、let name = contact["name"]
は引数のDictonary<String, String>のキーに"name"がなければfalseになるという感じでした。
func recipient(contact: [String: String]) -> String? { guard let name = contact["name"], let mail = contact["mail"] else { return nil } return "\(name) <\(mail)>" }
157> var contact2 = ["name" : "Taro"] contact2: [String : String] = 1 key/value pair { [0] = { key = "name" value = "Taro" } } 158> recipient(contact2) $R16: String? = nil
Closures
Rubyのブロックに近いです。関数は名前の付いた特別なClosureみたいな扱いであるところも似ています。
引数以外の変数をクロージャが定義されたコンテキストからキャプチャする
というのは、mapなどの引数のClosureの内部でその周囲で宣言した変数を、Closureの内部の独自の変数としてコピーして利用出来る、ということらしいのですが、ややこしいので後述します。
168> let outsideClosure = "outside" outsideClosure: String = "outside" 169> let values = ["first", "second", "third"] values: [String] = 3 values { [0] = "first" [1] = "second" [2] = "third" } 170> values.map({ (rank: String) -> String in 171. return "\(rank) \(outsideClosure)" 172. }) $R17: [String] = 3 values { [0] = "first outside" [1] = "second outside" [2] = "third outside" }
イテレータっぽいことをするメソッドの引数が複数の命令の塊であるClosure、という感じなのです。
一行目で、Closureの引数と戻り値を (rank: String) -> String
のように指定し、そのあとにinと書いてからClosureで行う処理の中身を返します。
Closureを渡すメソッドによって、Closureの戻り値によって配列を作ったり(map
)、合計値を作ったり(reduce
)、順番に処理を行うだけだったり(forEach
)します。
詳しくはこちら http://qiita.com/mo_to_44/items/cf83b22cb34921580a52
173> values.forEach({ rank in print("\(rank)")}) first second third 177> let array = [1,2,3,4,5] array: [Int] = 5 values { [0] = 1 [1] = 2 [2] = 3 [3] = 4 [4] = 5 } 178> array.reduce(0, combine: +) $R21: Int = 15
Closureの中の引数は渡される順に $0
, $1
, $2
...のようにかけるそうですが、意味が分かりにくくなりそうで$0
以外(できたら$0
も)使いたくないですね...
キャプチャ
キャプチャとは、Closureの中でClosureの外側で定義された変数を使うと、外側の変数とは別物としてコピーされて、Closureが持っている変数のように扱われることみたいです。
下記のようなcounter
関数があったとします。
229> func counter() -> () -> Int { 230. var count = 0 231. print("outside of closure \(count)") 232. let closure: () -> Int = { 233. print("inside of closure \(count)") 234. count += 1 235. return count 236. } 237. return closure 238. }
counter()関数が実施されるたびに、値が0のcount変数をキャプチャした新しいClosureが返されます。
countの値が0の値をキャプチャした新しいClosueがそれぞれ1足してかえすだけなので、counter()()
を続けて何度実行しても、その返り値は1です。
239> counter() outside of closure 0 $R42: () -> Int = 0x0000000101bb0000 $__lldb_expr251`__lldb_expr_250.(counter () -> () -> Swift.Int).(closure #1) at repl250.swift 240> counter() outside of closure 0 $R43: () -> Int = 0x0000000101bb0000 $__lldb_expr251`__lldb_expr_250.(counter () -> () -> Swift.Int).(closure #1) at repl250.swift 241> counter()() outside of closure 0 inside of closure 0 $R44: Int = 1 242> counter()() outside of closure 0 inside of closure 0 $R45: Int = 1
Closureを戻り値として受け取って、このClosureを何度か実行してみます。
ここで、実行される部分は、
232. let closure: () -> Int = { 233. print("inside of closure \(count)") 234. count += 1 235. return count 236. }
だけです。
couter()
メソッドにより、count変数の値が初期化されて、それを改めてキャプチャして、という部分は通らないので、実行するたびに値が増えます。
まるでうまく説明できている気がしませんが、
232. let closure: () -> Int = { // フラグの値を見て最初だけinsidecountにcountを代入する // フラグを偽にする 234. insidecount += 1 235. return insidecount 236. }
という処理を行っているような感覚でした。
243> var countClosure = counter() outside of closure 0 countClosure: () -> Int = 0x0000000101bb0000 $__lldb_expr251`__lldb_expr_250.(counter () -> () -> Swift.Int).(closure #1) at repl250.swift 244> countClosure() inside of closure 0 $R46: Int = 1 245> countClosure() inside of closure 1 $R47: Int = 2 246> countClosure() inside of closure 2 $R48: Int = 3 247> countClosure() inside of closure 3 $R49: Int = 4
この一度変数として受け取ったClosureは元のcounterメソッドにより新しく呼びだされるClosureとは別物なので、counterメソッドによって新しく作成されるClosureにキャプチャされるcount変数の値が0であろうと、countClosureの中のcount変数の値は初期化されたりしません。
248> counter() outside of closure 0 $R50: () -> Int = 0x0000000101bb0000 $__lldb_expr251`__lldb_expr_250.(counter () -> () -> Swift.Int).(closure #1) at repl250.swift 249> countClosure() inside of closure 4 $R51: Int = 5 250> let anotherClosure = counter() outside of closure 0 anotherClosure: () -> Int = 0x0000000101bb0000 $__lldb_expr251`__lldb_expr_250.(counter () -> () -> Swift.Int).(closure #1) at repl250.swift 251> anotherClosure() inside of closure 0 $R52: Int = 1
参照型の値を扱うときはもうちょっと注意してみてみますね...。
Value types
Swiftの値型には enum
と struct
があります。
enum ArithmeticOperation { case Add case Subtract case Multiply case Divide }
enum型は連想型を持つことができます。
enum Diagram { case Line(Double) case Rectangle(Double, Double) case Circle(Double) }
enumは定義した変数名だけでなく、それに相当する定数値(raw value)などを直接指定することもできます。
enum Month: Int { case January = 1, February, March, April, May, June, July, August, September, October, November, December } if let april = Month(rawValue: 4) { print(april.rawValue) }
再帰的なデータ構造を作成したい場合は、enum
やcase
にindirect
を前置することで associated value が間接的に格納されます。
enum List<T> { case Nil indirect case Cons(head: T, tail: List<T>) }
Structures
struct
は構造体をつくります。
構造体をインスタンス化する際に与えられた引数がvarであってもletであっても、構造体の変数の定義がletで定義されていたら変更することはできません。
また、構造体をインスタンス化する際に与えられた引数がletであっても(普通に定数を渡した場合がこれ)、変更することはできません。
252> struct Pair { 253. let x : Double 254. let y : Double 255. } 256> var sample = Pair(x: 1.2, y: 2.2) sample: Pair = { x = 1.2 y = 2.2000000000000002 } 257> sample.x $R53: Double = 1.2 258> sample.x = 4.0 repl.swift:258:10: error: cannot assign to property: 'x' is a 'let' constant sample.x = 4.0 ~~~~~~~~ ^ repl.swift:253:5: note: change 'let' to 'var' to make it mutable let x : Double ^~~ var 258> struct MutablePair { 259. var x : Double 260. var y : Double 261. } 262> var sample2 = MutablePair(x: 1.2, y: 2.2) sample2: MutablePair = { x = 1.2 y = 2.2000000000000002 } 263> sample.x = 3.0 repl.swift:263:10: error: cannot assign to property: 'x' is a 'let' constant sample.x = 3.0 ~~~~~~~~ ^ repl.swift:253:5: note: change 'let' to 'var' to make it mutable let x : Double ^~~ var 263> var pairX = 1.2 pairX: Double = 1.2 264> var pairY = 2.2 pairY: Double = 2.2000000000000002 265> var sample3 = MutablePair(x: pairX, y: pairY) sample3: MutablePair = { x = 1.2 y = 2.2000000000000002 } 266> sample3.x = 3.0 267> sample3.x $R54: Double = 3
Structは複数の値の組を扱いたいときに作って使うと便利かもしれません。
Value type
値型の変数は、その間で状態を共有しないので、値を変数に代入したり関数に引数として渡したりしたときに、その内容がコピーされたと見なすことができます。
あと2日くらいあればなんとか...。