optional,map,クロージャに関して

理解が少しあやふやだった、.map、クロージャ、optinalについて詳しく書いた記事があったため少しまとめてみた。 https://qiita.com/koher/items/c6f446bad54442a28bf4

optional(1)

Optionalやobservableは、一つの箱として考える。intやstringも一つの箱になるので、observableには、2つの箱が用いられていると考えることができる。 毎回、この箱を開けて処理を書くのはめんどくさい。だから、mapやflatmapを使って箱の中身だけを処理する。 第1級関数 ・変数に代入する ・関数やメソッドの引数として渡す ・関数やメソッドの戻り値として返す

高階関数 関数を引数として、受けたり関数の戻り値を引数に受けて処理を行う関数のこと。 メソッド高階関数に当たる。

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の場合は除外もしてくれる。 この後の、モアドに関しては、理解が出来次第まとめる。