optional,map,クロージャに関して
理解が少しあやふやだった、.map、クロージャ、optinalについて詳しく書いた記事があったため少しまとめてみた。 https://qiita.com/koher/items/c6f446bad54442a28bf4
optional(1)
Optionalやobservableは、一つの箱として考える。intやstringも一つの箱になるので、observable
高階関数
関数を引数として、受けたり関数の戻り値を引数に受けて処理を行う関数のこと。
メソッド
も高階関数に当たる。
func map<U>(f: T -> U) -> U?
Tとして受け取った型をUに直す関数をmapして、Uとして返す。
let a: Int? = 3 a.map(square) func square(x: Int) -> Int { return x * x }
この場合、squareはint型をintとして返す関数と考えることができる。
func map<U>(f: T -> U) -> U?
ここに当てはめると、
func map(f: Int -> Int) -> Int?
とint型がここに当てはまる。
これらの処理を毎回行うたびに関数を新しく作り、関数名を考えることは手間がかかる。 そこで、クロージャを利用する。
先ほど行なったsquareの処理をクロージャに書き直してみる。
let square: Int -> Int = { (x: Int) -> Int in return x * x }
xをIntの引数として、受け取ったものをintで返す関数を引数として受け取るsquareのクロージャを作る。
Squareは、intで受け取ったものをx * x
という処理を行いreturn
してint型に返している。
一行だけのクロージャは、retrurnを書かずにでも処理することができるため
let square: Int -> Int = { (x: Int) -> Int in x * x }
という書き方できる。
次に、型推論が行われるため、
let square:Int -> Int = { x in x * x }
と書く。 最後に、理解がむずかしたかった。クロージャの第一関数を$0に直すという処理。
let square: Int -> Int = { $0 * $0 }
xという引数を$0と書くことで、毎回引数のラベルを指定する手間を省くことができる。 この、squareというクロージャをmapを使って書くと
let a: Int? = 3 let result: Int? = a.map({ $0 * $0 })
a.mapでmapの後ろに書かれた処理ができる。
.mapの引数であるaが$0に入り{ $0 * $0 }
という処理が行われる。
そこで行われた処理を、変数resultに格納するという処理が行われている。
Swiftでは、関数の引数にクロージャが渡された場合クロージャ を外出しすることができる。
これを、trailing closure
と呼ぶ。
let result: Int? = a.map() { $0 * $0 }
このように書くことができ、( )も外すことが出来るため
let result: Int? = a.map { $0 * $0 }
と書くことが出来る。
optional(2)
let number: Int? = String.toInt()
上記は、string型をintに直す関数である。
String型に数字が流れてきた場合は、int型に直すことができます。
しかし、何かの手違いで”abc”のようなstring型が流れるとint型に直せないためこの処理ではnil
が流れます。
そこで、numberには?をつけてnilを許容する必要がある。
Swift以外の言語では、nilに対する処理を書かなくてよかったため、エラーが頻繁に起こった。
2. array: [Int]? があるとき、 array の最初の要素を取得したい。ただし、 array が nil か、最初の要素が存在しない( Array が空の)場合は nil を得たい。
let result: Int?? = array.map { $0.first }
Mapを行うと、optionalで値を返すためoptionalの型の中にotionalが入るという2重構造になる。 つまり、2度nilを許容する必要があるため??となる。 そこで、optional chainingを行なって
let result: Int? = array?.first
と書く。arrayに値があった場合.firstでarrayの一番最初の値を取ってくる。
3. x: Double? があるとき、 x の平方根を計算したい。ただし、 x が nil または負の数の場合は nil を得たい。なお、 sqrt を安全にした(負の数を渡すと nil を返す)関数 safeSqrt: Double -> Double? があるものとして考えて良い。 3では、負の数をnilで返す関数
safeSqrt
が用意されている。 しかし、Optional chaining
は、 Array.firstのように箱の中身に対してメソッドをコールすることはできても、 箱の中身を引数に渡すことはできません。つまり、safeSqrt
を行うことができない。 mapを使えば、箱の中身を引数にとった処理が行えるため、下のように書くことが出来る。
let result: Int?? = x.map { safeSqrt($0) }
しかし、mapを行なったことでoptionalの型がつきnilの許容性を持つ必要が出ます。 二重の箱が邪魔なので外側の箱だけ開けるために、 ! で開けるわけにはいきません。もし外側の箱がnilだった場合落ちる可能性が生まれる。 そこで、 ??を使う。??は、もし箱が空だった場合の代わりを書くことが出来る。 !は無理やり開けるだけみたいなイメージ。 ??を使うと開けて空だったら、何かを処理しているため少し丁寧なイメージ笑
これで、一つの箱を開けることが出来た。
let result: Int? = x.map { safeSqrt($0) } ?? nil
上記では、?だった場合nilを渡しているので!とは違い、落ちる可能性がない。
flatMap
a.flatMap { safeSqrt($0) }
.flatMapは、mapの結果の二重の箱の処理を行うことが出来る。
4. a: Int? と b: Int? があるとき、 a + b を計算したい。ただし、 a か b が nil の場合には nil を得たい。 箱の蓋を開けずに処理を行うと
let result: Int?? = a.map { a0 in b.map { b0 in a0 + b0 } }
と書くことが出来る。 a.mapとb.mapとmapの入れ子構造としてできている。
ここで、自分自身でも理解できていなかったためmapとflatmapについてもう少し理解を深める。 Mapは、optionalにも利用することが出来る。 Optinalの値がnilの場合は、何も処理を行わず、nil以外なら処理を行うという式を書くことが出来る。
let value: Int? = 1 var result: Int? = value.map { $0 + 1 } // ↑ と ↓ は同じ意味 result = value != nil ? value! + 1 : nil
このように、optional chainingとして利用できる。 Optionalの場合はguardを用いることが出来るため、使用頻度は少ない。
[[1, 2], [3]].flatMap { $0 } // → [1, 2, 3] [[1, 2], [3]].flatMap { $0 + [1] } // → [1, 2, 1, 3, 1]
このようにflatmapを用いると、格配列を1重にしてくれる。
["1", "2", "string", "3"].map { Int($0) } // → [Optiona(1), Optiona(2), nil, Optiona(3)] ["1", "2", "string", "3"].flatMap { Int($0) } // → [1, 2, 3]
中身がnilの場合は除外もしてくれる。 この後の、モアドに関しては、理解が出来次第まとめる。