クロージャーの仕様

クロージャーの話
ソート
トレイルクロージャー
キャプチャー

クロージャーの話

概念の話を英語でされても困るっつうの。

クロージャーには3タイプあるらしい。
グローバル関数(Global functions)……関数名を持っていて、キャプチャーしている変数や定数はない
関数の中に作った関数(Nested functions)……関数名を持っていて、外側の関数の変数や定数をキャプチャーしている
Closure expression(訳がわからんちん)……関数名を持たない。簡素簡単に書いたやつ。で、こいつを囲ってるやつの変数や定数をもってる
訳があっているかわからんな。キャプチャーというのはアクセス可能だとか見えているとかいう意味だと思われる。
詳しくは最後の項で。

以下、ガチで関数定義しないでさくさくっと書いちゃう方法。

ソート

次の配列をソートすることを考える。

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

で、ソートする関数sortは引数を2つとる。1つ目が配列。型がちゃんとわかってるやつ。
2つ目に関数。引数が2つ(ソートしたい配列の要素と型が同じもの)で、boolを返すやつ。こいつからtrueが返ると1つ目の引数が2つ目の引数より先頭に、falseが返ると後ろ側にソートされる。

func backwards(s1: String, s2: String) -> Bool {
	return s1 > s2
}
var reversed = sort(names, backwards)	//["Ewa", "Daniella", "Chris", "Barry", "Alex"]

とまあこんな感じでsortを使う。この場合、backwardsは文字列を比較して、s1が(辞書的に)大きければs1をs2より前にソートするとなるので、逆アルファベット順にソートされる。

以上のようなやり方でもいいのだが、わざわざきっちり関数名まで定めて関数を定義するのもなぁ、必要なの「s1 > s2」の部分だけだし、みたいになるので、Closure Expression Syntaxとか英語でいう、まあ早い話がsortの引数の中でその場限りの関数を定義しちゃおうみたいな。

/*
{(引数: 型, ...) -> 返り値型 in
	命令
}
*/
reversed = sort(names, {(s1: String, s2:String) -> Bool in
	return s1 > s2
	})

とまあ、中かっこの中に引数の型も返り値の型も書いてしまう。ここまで簡略化されれば1行でソートに必要なことがかけてしまう。

reversed = sort(names, { (s1: String, s2: String) -> Bool in return s1 > s2 } )

なんかもうここまで来るとsortくんには配列から型とか察してくれよとか、どうせ返り値boolだろ、となるので、sortに与える関数の型は省略してOKだったりする。

reversed = sort(names, { s1, s2 in return s1 > s2} )

sortに限らず、swiftはインラインで書くクロージャーの型に関しては頑張って解釈してくれるようだ。

で、命令が1つしかない場合、returnすら省略できる。命令1つしかないしsortの第2引数に入ってるあたり返り値boolで確定だしこれでいいだろって感じである。

reversed = sort(names, { s1, s2 in s1 > s2} )

さらにもっというと、特に仮引数名を付ける必要がないなら、次の書き方でもいい。

reversed = sort(names, { $0 > $1 } )

要するに、仮引数にわざわざ名前を与えずとも仮引数には先頭から$0、$1、$2……というのが自動で与えられているので、それを使って命令を書くだけということである。

これで最後。
演算子単品でもswiftは察してくれる。

reversed = sort(names, >)

トレイルクロージャー

ここまで、クロージャーの形で関数を渡すことをやってきた。

//closureという名の関数の仮引数をもつ関数
func someFunctionThatTakesAClosure(closure: () -> ()) {
	//someFunctionThatTakesAClosureの処理
}

//実際にsomeFunctionThatTakesAClosureを使ってみる。
someFunctionThatTakesAClosure({
	//クロージャーの処理を書く
})

場合によっては引数のかっこ内で処理を書くには量が多くて……なんてこともあるかもしれない。そんなときは別にカッコの中に書く必要がなかったりする。

someFunctionThatTakesAClosure() {
	//クロージャーの処理を書く
}

なのでさっきのソートもこんな書きかたができる。ここまでくると変わんねえけど。

reversed = sort(names) { $0 > $1 }

sortではもはや恩恵が受けられないので、map関数を使ってみる。配列の要素全員に処理を施してくれるヤツであり、引数は1つ(やりたい処理を書いた関数)だけである。

let digitNames = [
	0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
	5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]

今ここに2つの配列があり、digitNamesには数字(Int)に対応する英単語(String)が、numbersには適当な数字が並んでいる。で、この適当な数字を英単語で表すことを考える。
つまり、16からOneSix、58からFiveEight、510からFiveOneZeroを作りたい。

let strings = numbers.map {
	(var number) -> String in
	var output = ""
	while number > 0 {
		//どんなname % 10でも必ずdigitNamesから値が得られることを保証するための「!」。
		output = digitNames[number % 10]! + output
		number /= 10
	}
	return output
}

本来mapは
配列.map(関数)
とするべきところだが、カッコの中に書き切るにはいかんせん量が多いので、トレイルクロージャーの機能を利用して
配列.map() {関数}
とここでは書いている。ように見せかけてmap()の小かっこがない。引数が1つしかなく、かつそのたった1つの引数にトレイルクロージャーを渡すのであれば()が省略可能なんである。

キャプチャー

キャプチャーの日本語訳がわからないのだが。
まあ要するに以下の話、クロージャー外の変数や定数にアクセスできちゃってるよねっていうこと。

//Int型を受け取って、() -> Int関数を返す
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
	var runningTotal = 0
	func incrementor() -> Int {
		runningTotal += amount
		return runningTotal
	}
	return incrementor
}

//呼び出されるたび返り値が10ずつ増える関数の完成
let incrementByTen = makeIncrementor(forIncrement: 10)
//1回目:10が返る
incrementByTen()
//2回目:20が返る
incrementByTen()
//3回目:30が返る
incrementByTen()

//呼び出されるたび返り値が7ずつ増える関数の完成
//incrementByTenとは独立して動作する
let incrementBySeven = makeIncrementor(forIncrement: 7)
//1回目:7が返る
incrementBySeven()
//4回目:40が返る
incrementByTen()

incrementor()は自分の中にはrunningTotalやamountといったものの宣言がないが、問題なくそいつらをいじることができている。
ちなみに、incrementByTenとかを別のやつに代入するとコピー体が出来上がる。

let alsoIncrementByTen = incrementByTen
//5回目:50が返る
alsoIncrementByTen()

↑ PAGE TOP