Swift超入門_関数とクロージャ的な何か
仮引数と省略 swift初心者(おっさん)が一見して理解できなかったのが関数の仮引数の前にあるアンダースコア(_)。 swift全般的にアンダースコアが言語仕様に含まれているので、いろいろ調べてみた。 単に、関数を呼び出す際にキーワード引数の名称を省略するための記法だった。 仮引数の前にアンダースコア(_)を置くと、呼び出し時にキーワード無しで引数を呼べる。 func hoge(_ int: Int) { print(int) } hoge(100) func hoge2(int: Int) { print(int) } hoge2(int: 200) Swiftは、仮引数と実引数を違う名前で定義できる。 以下、param2というキーワードを使ってhoge3を呼び出しているが、 hoge3の中では仮引数param2をparam3として使っている。 一般に、param3をparam1と関係がある名称にすることで冗長にさせない工夫。 func hoge3(param1: Int, param2 param3: Int) { print(\"(param1),(param3)\") } hoge3(param1: 100, param2: 200) デフォルト引数は一般的。 func hoge4(param1: Int, param2 param3: Int = 200) { print(\"(param1),(param3)\") } hoge4(param1: 100) 関数の定義名に特徴がある。hoge4の定義名は以下のようになる。 ObjectiveCっぽい。 hoge4(param1:param2) インアウト引数 いわゆる参照渡し。 仮引数にinout予約語をつけておくと、仮引数の操作が実引数に反映される。 呼び出す際に、引数に&をつける。 func hoge5(param1: Int, param2: inout Int) { param2 = 300 print(param1, param2) } var val1 = 100 var val2 = 200 hoge5(param1: val1, param2: &val2) // 100 200 print(val2) // 300 可変長引数 可変長引数はスリードット(...)を指定し、配列(Array)で受ける。 func hoge6(params: String...) { for param in params { print (\"element is (param)\") } } hoge6(params: \"hoge1\",\"hoge2\",\"hoge3\") クロージャ さて、Swiftのクロージャ。まぁ何回か使ったら慣れるだろうぐらい分かりづらくはない。 前置詞のinがクロージャブロックの開始になってるのは意図があるんだろうか。 let double = { (x:Int) -> Int in return x * 2 } let v = double(2) クロージャの引数の型と戻り値の型は型推論が効く。 var closure: (Int) -> Int closure = { (x:Int)-> Int in return x*x } closuer(2) // 4 var closure2 = { x in return x*2 } closure2(3) // 6 クロージャの簡易引数名 クロージャの引数名すら書きなくない場合もある。 前置詞inではなく、一般的なブラケットでクロージャブロックを書く。 クロージャブロックからは、n番目のパラメタを$nとして受け取る。 let add: (Int,Int)->Int = { return $0+$1 } add(2,3) // 5 クロージャによる変数束縛 クロージャの実行時にクロージャ呼び出し元の環境を利用できる。 文脈によって呼び出し元の環境は異なるので、実行時に環境のスナップショットを取るイメージ。 Swiftでは\"キャプチャ\"という名前で呼んでいるらしい。 例えば以下。クロージャ add:(Int,Int)-> Int 内で呼び出し元の変数pを使っている。 クロージャ呼び出し時に呼び出し元の環境に依存してpの値が決まる。 なお、pが初期化済みでない状態でクロージャを定義するとエラーになる。 クロージャの実行前にキャプチャする変数は初期化されている必要があるらしい。 var p : Int = 200 let add: (Int,Int)->Int = { return $0+$1 + p } p = 200 add(2,3) // 205 p = 300 add(2,3) // 305 クロージャ内でキャプチャした変数を書き換えた場合、キャプチャ元になった変数自体が変わる。 autoclosure属性 引数の遅延評価を行う指令。 論理和を計算する関数を定義しようとしたとき、Bool型の引数を2つ取ってBool型の戻り値を返す 関数を定義するとする。 論理和なので、どちらかが真であれば一方の引数を見なくても結果がわかるのだが、 下記のようにすると、どちらかが真であっても、もう一方の引数を評価して返すことになる。 func or(_ lhs:Bool, _ rhs:Bool) -> Bool { if lhs { return true } else { return rhs } } or(true,false) 引数をクロージャとし、クロージャ内で引数の評価の必要性を判断するようにして、 クロージャを遅延評価(関数内での評価)することで不要な引数の評価を回避するというアイデア。 func or2(_ lhs:Bool, _ rhs:()->Bool) -> Bool { if lhs { return true } else { let rhs = rhs() return rhs } } func lhs() -> Bool { return true } func rhs() -> Bool { return false } or2(lhs(),{return rhs()}) or2の第2引数が複雑になってしまうところを、文法解決で簡単に書けるようにするのが@autoclosure。 引数をクロージャで包む書き方を省略できる。 下記のように書ける。 func or3(_ lhs:Bool, _ rhs: @autoclosure ()->Bool) -> Bool { if lhs { return true } else { let rhs = rhs() return rhs } } func lhs() -> Bool { return true } func rhs() -> Bool { return false } or3(lhs(),rhs()) trailing closure 引数の最後がクロージャである場合、その引数の前で引数のブラケットを閉じて、 その後にクロージャを書くことができる。 例えば以下のような感じ。 1つ目は真面目に引数の最後にクロージャを与えている。 2つ目は関数呼び出しの後にクロージャを書いている。 func execute(parameter:Int, closure: (String)->Void) { closure(\"parameter is (parameter)\") } execute(parameter:1, closure:{ string in print(string) }) execute(parameter:2) { string in print(string) } 関数をクロージャとして扱う 関数をクロージャとして扱うことができる。関数double(_:)は(Int)->Int型。 それをfunctionという定数の初期値に設定している。 functionには型アノテーションがないけれども、 double(_:)が(Int)->Intなので型推論されて(Int)->Intになる。 func double(_ x:Int)->Int { return x*2 } let function = double function(2) 関数の引数としてクロージャを与えるシーンで、 クロージャを関数として用意しておくことで、それぞれの呼び出しでクロージャを定義する必要がなくなる。 下記について、上はmapの引数としてそれぞれクロージャを定義して呼び出している。 同じ処理をするのに2回定義しないといけない。 下は、関数を定義し、mapの引数として関数をクロージャとして与えている。 関数(クロージャ)の定義は1回で良い。 let array1 = [1,2,3] let toArray1b = array1.map{$0*2} let array2 = [4,5,6] let toArray2b = array2.map{$0*2} func double(_ x:Int)->Int { return x*2 } let toArray1a = array1.map(double) let toArray2a = array2.map(double)