Swift覚え書き(2)〜クロージャの使い方〜
https://waarumstudy.blogspot.com/2018/10/swift.html
クロージャ(closure)についての動画講座を見たので自分なりにまとめて見ました。
名前がないってどう言うことだ?わからん!
と言うことで、まずは関数と比べながら何が違うのかを見ていきましょう。
呼び出すときはこのような形式。
具体的にはこんな使い方をします。
(xと言う定数を宣言するときに関数addの戻り値を代入しています)
calculatorという関数ではoperationという名前の引数として関数を引数に指定しています。
calculatorの戻り値では引数の関数に2つの数値を入れて値を計算させています。
例えば次のような使い方ができます。
与えられた引数2つを足した値を返す関数addをcalculatorの引数とすることで足し算を行なっています。
もちろん掛け算や引き算など他の計算を行う関数を引数とすることもできます。
関数を引数にして処理をさせるのはわかったけど、それじゃ変な関数を入れた時にエラーになっちゃうんじゃない?…
という疑問が湧きますね。
でも、大丈夫。
関数を引数に指定している部分をもう一度見てみましょう。
そんな時はクロージャを使って処理そのものを引数に指定してやると、無駄な関数を作らずに済ませることができます。
こういう処理が・・・
1行で済んじゃいます。
え?クロージャを書くのも関数を書くのも手間は変わらないんじゃないかって?
実はクロージャはもっと短くできるんです。
例えば引数や戻り値の型。
これらはクロージャが書かれた関数でどんな処理がされているかをSwiftが判断して補ってくれるので、省略できます。
さらに、クロージャの引数の名前も省略できちゃいます。
省略した場合、一つ目の引数は$0、二つ目の引数は$1というように扱われます。
そしてそして、上のように省略されたクロージャが関数の最後の引数のとき、クロージャを指定する引数の名前まで省略できちゃいます。
その場合のクロージャは引数の括弧の外に書きます。
ここまで短くできるならクロージャを使ってみるのもいいかもと思ったんじゃないでしょうか。
クロージャって何?
よくある説明
一言で言うと、名前の無い関数です
・
・
・
名前がないってどう言うことだ?わからん!
と言うことで、まずは関数と比べながら何が違うのかを見ていきましょう。
関数の形
関数の基本的な形は下のようなものですね。(引数の数は問いません)func 関数名(引数1の名前: 引数1の型, 引数2の名前: 引数2の型) -> 戻り値の型 { return 戻り値 }
具体的に名前を入れてみるとこのようになります。
func add(n1:Int, n2:Int) -> Int { return n1 + n2 }
呼び出すときはこのような形式。
関数名(引数1の名前:引数1の値, 引数2の名前:引数2の値)
具体的にはこんな使い方をします。
(xと言う定数を宣言するときに関数addの戻り値を代入しています)
let x = add(n1:1,n2:2)
関数からクロージャを作る
では、この関数を書き換えてクロージャにしてみます。
まず、クロージャには名前がないので、名前をつけている部分をfuncキーワードごと削除します。
func 関数名(引数1の名前:引数1の型, 引数2の名前:引数2の型) -> 戻り値の型 { return }
↓
(引数1の名前:引数1の型, 引数2の名前:引数2の型) -> 戻り値の型 {
return
}
次に、戻り値の型の後に出てくる「{」を頭に持っていきます。
(引数1の名前:引数1の型, 引数2の名前:引数2の型) -> 戻り値の型 { ←これ
return 戻り値
}
↓
{(引数1の名前:引数1の型, 引数2の名前:引数2の型) -> 戻り値の型
return 戻り値
}
そして戻り値の後に「in」を書き足します
{(引数1の名前:引数1の型, 引数2の名前:引数2の型) -> 戻り値の型 ここ
return 戻り値
}
↓
{(引数1の名前:引数1の型, 引数2の名前:引数2の型) -> 戻り値の型 in
return 戻り値
}
これでクロージャの形ができました。
「どういうこと?」とはてなマークが浮かんだ方、次の関数をご覧ください。
クロージャって何に使うの?
その前に・・・
クロージャの使い方を理解する土台となる2つの項目を確認しましょう。
1つ目:関数の引数としての関数
先ほどクロージャの作り方でも見たように、関数は値を引数として持つことができます。
実は、引数になるのは値だけではありません。
別の関数を引数にすることもできるのです。
実は、引数になるのは値だけではありません。
別の関数を引数にすることもできるのです。
「どういうこと?」とはてなマークが浮かんだ方、次の関数をご覧ください。
func calculator(n1: Int, n2: Int, operation:(Int,Int) -> Int) -> Int {
return operation(n1, n2)
}
calculatorという関数ではoperationという名前の引数として関数を引数に指定しています。
calculatorの戻り値では引数の関数に2つの数値を入れて値を計算させています。
例えば次のような使い方ができます。
func add(n1:Int, n2:Int) -> Int {
return n1 + n2
}
func calculator(n1: Int, n2: Int, operation:(Int,Int) -> Int) -> Int {
return operation(n1, n2)
}
print(calculator(n1: 1, n2: 3, operation: add))
// 4が出力される
与えられた引数2つを足した値を返す関数addをcalculatorの引数とすることで足し算を行なっています。
もちろん掛け算や引き算など他の計算を行う関数を引数とすることもできます。
関数を引数にして処理をさせるのはわかったけど、それじゃ変な関数を入れた時にエラーになっちゃうんじゃない?…
という疑問が湧きますね。
でも、大丈夫。
関数を引数に指定している部分をもう一度見てみましょう。
func calculator(n1: Int, n2: Int, operation:(Int,Int) -> Int) -> Int {
return operation(n1, n2)
}
(Int,Int) -> Intは、「Int型の値二つを引数とし、Int型の値を返す型の関数」を意味しています。
引数として入れることができるのはこの型の関数だけなので、calculator内の処理でエラーが起きることはありません。
実は関数にも型があり、引数に何型の値をいくつとるか、何型の値を返すかによって決まります。
どちらのやり方でも結果を計算できるので、状況によって以下のように使い分けることになると思います。
何を当たり前のことを…
と、思ったかもしれません。
でも、これがクロージャの使い方に関わってくるんです。
引数として定数だけでなくただの値が使えたように、引数としての関数の代わりにクロージャを使うことができます。
関数addをクロージャの形にするとこのようになります。
これをaddの代わりに入れてやるとこのようになります。
引数として入れることができるのはこの型の関数だけなので、calculator内の処理でエラーが起きることはありません。
実は関数にも型があり、引数に何型の値をいくつとるか、何型の値を返すかによって決まります。
2つ目:値は名前のない定数である
この言い方、どこかでみましたね
・
・
・
そう、クロージャのざっくりとした説明のところでも同じ言い方をしました。
また関数の引数を例に考えてみましょう。関数の引数には定数や変数(名前のある値)と値(名前のない値)の両方を指定できます。
func add(n1:Int, n2:Int) -> Int {
return n1 + n2
}
//名前がある場合
let a = 2
let b = 5
print(add(n1:a, n2:b))
// 名前がない場合
print(add(n1: 2, n2: 5))
- 何度も使う値の時は定数
- 他の関数などで計算した値を使いたいときは変数
- 1回切りの時は名前のない値
何を当たり前のことを…
と、思ったかもしれません。
でも、これがクロージャの使い方に関わってくるんです。
クロージャの使い方
ここまでの説明で何を言いたかったのか。それは次のような関係です。
定数⇔名前のない値関数⇔クロージャ
引数として定数だけでなくただの値が使えたように、引数としての関数の代わりにクロージャを使うことができます。
先ほどのcalculator関数でみてみましょう。
普通に関数を引数にするとこのようになります。
func calculator(n1: Int, n2: Int, operation:(Int,Int) -> Int) -> Int {
return operation(n1, n2)
}
print(calculator(n1: 1, n2: 3, operation: add))
関数addをクロージャの形にするとこのようになります。
{(a:Int, b:Int) -> Int in a + b}
これをaddの代わりに入れてやるとこのようになります。
print(calculator(n1: 3, n2: 4, operation: {(a:Int, b:Int) -> Int in a + b}))
関数の代わりにクロージャを入れてもきちんと計算することができます。
クロージャを使うメリットは?
実は私自身まだ勉強中でクロージャをあまり使ったことがなく実感できていないのですが、コードがシンプルになるというメリットがあります。
1度しか使わない処理の関数を引数にしなければならない時に、いちいち関数を宣言してから引数として呼び出すのってまわりくどいですよね。そんな時はクロージャを使って処理そのものを引数に指定してやると、無駄な関数を作らずに済ませることができます。
こういう処理が・・・
func add(n1:Int, n2:Int) -> Int { return n1 + n2 } print(calculator(n1: 1, n2: 3, operation: add))
1行で済んじゃいます。
print(calculator(n1: 3, n2: 4, operation: {(a:Int, b:Int) -> Int in a + b}))
え?クロージャを書くのも関数を書くのも手間は変わらないんじゃないかって?
実はクロージャはもっと短くできるんです。
さらに短く!
Swiftの型推論のおかげで、クロージャを書く時に色々な省略ができます。例えば引数や戻り値の型。
これらはクロージャが書かれた関数でどんな処理がされているかをSwiftが判断して補ってくれるので、省略できます。
print(calculator(n1: 3, n2: 4, operation: {(a:Int, b:Int) -> Int in a + b}))
↓
print(calculator(n1: 3, n2: 4, operation: {(a, b) in a + b}))
さらに、クロージャの引数の名前も省略できちゃいます。
省略した場合、一つ目の引数は$0、二つ目の引数は$1というように扱われます。
print(calculator(n1: 3, n2: 4, operation: {(a, b) in a + b}))
↓
print(calculator(n1: 3, n2: 4, operation: {$0 + $1}))
そしてそして、上のように省略されたクロージャが関数の最後の引数のとき、クロージャを指定する引数の名前まで省略できちゃいます。
その場合のクロージャは引数の括弧の外に書きます。
print(calculator(n1: 3, n2: 4, operation: {$0 + $1}))
↓
print(calculator(n1: 3, n2: 4){$0 + $1})
ここまで短くできるならクロージャを使ってみるのもいいかもと思ったんじゃないでしょうか。
最後に
クロージャについて、私なりの理解の仕方を説明してみました。
関数からクロージャを作る説明はほぼ受け売りですが、名前のない値と比較する説明はオリジナルです。
関数からクロージャを作る説明はほぼ受け売りですが、名前のない値と比較する説明はオリジナルです。