woshidan's blog

あいとゆうきとITと、とっておきの話。

はてな教科書の「プログラミング言語 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の値型には enumstruct があります。

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

再帰的なデータ構造を作成したい場合は、enumcaseindirectを前置することで 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日くらいあればなんとか...。