Skip to content

Instantly share code, notes, and snippets.

@tarunon
Last active August 26, 2020 10:04
Show Gist options
  • Save tarunon/2e37aa4e4c702f65e49a322c80dee13f to your computer and use it in GitHub Desktop.
Save tarunon/2e37aa4e4c702f65e49a322c80dee13f to your computer and use it in GitHub Desktop.
>>=.swift
// tryがResult.flatMapのdo記法であることの確認。
// 逆になるが、Result.flatMapから考えていったほうが理解しやすいのでその順番で。
func foo() -> Result<Int, Error> {
.success(1)
}
func bar(_ arg: Int) -> Result<Int, Error> {
.success(arg + 1)
}
// 他の関数も存在すると仮定する
let hoge = bar
let fuga = bar
let piyo = bar
// fooとbarの関数を合成することを考える。
// fooが成功した場合、その成功した値をbarの引数に渡したい。
// ResultはモナドなのでflatMapを使う。
func composeUsingFlatMap() -> Result<Int, Error> {
foo().flatMap { bar($0) }
}
// 2つだと難しくは無いが、例えば合成したい関数が増えるとflatMapを大量に呼ばなければならなくなる、
// またflatMapをコールする以上、変数を挟むことがMustとなる、という不満が出てくる
// これは大量のflatMapを呼んでいる例。
func tooManyFlatMap() -> Result<Int, Error> {
return foo()
.flatMap { bar($0) }
.flatMap { hoge($0) }
.flatMap { fuga($0) }
.flatMap { piyo($0) }
}
// もしResultでなくIntを返す関数であるなら、1行で関数合成することも可能だ。
func composeCommonFunctions() -> Int {
func foo() -> Int { 1 }
func bar(_ arg: Int) -> Int { arg + 1 }
let hoge = bar
let fuga = bar
let piyo = bar
return piyo(fuga(hoge(bar(foo()))))
}
// そこで出てくるのがdo記法。SwiftだとResult.flatMapに対してはtryが相当する。
// .get()でthrowsに変換出来るので、それを利用する。
// 大量のflatMapが必要無くなったことが解る。
func composeUsingTryAndTmp() throws -> Int {
let a = try foo().get()
let b = try bar(a).get()
let c = try hoge(b).get()
let d = try fuga(c).get()
return try piyo(d).get()
}
// もちろん変数を経由せずに一行にしても良い。
func composeUsingTry() throws -> Int {
try piyo(try fuga(try hoge(try bar(try foo().get()).get()).get()).get()).get()
}
// 1行に1つのtryも可能だが、これは上記の省略になる。
func composeUsing1Try() throws -> Int {
try piyo(fuga(hoge(bar(foo().get()).get()).get()).get()).get()
}
// ここまでがtryがResult.flatMapのdo記法であることの解説。
// これを踏まえた上で以下を考える。
func composeUsing1TryOptional() -> Int? {
try? piyo(fuga(hoge(bar(foo().get()).get()).get()).get()).get()
}
// もちろん関数合成した上で、結果のthrowsをnilにしてOptionalに変換してる、という考え方もアリ。
// 或いは、それぞれの関数にtry?がかかっている、という考え方も出来る。その場合対応するflatMapはOptional.flatMapになり、flatMap記法で表現すると下記となる。
func composeUsingTryOptionalAndFlatMap() -> Int? {
(try? foo().get())
.flatMap { (try? bar($0).get()) }
.flatMap { (try? hoge($0).get()) }
.flatMap { (try? fuga($0).get()) }
.flatMap { (try? piyo($0).get()) }
}
// SwiftにはOptional.flatMapのdo記法は一応存在していて、Optional Bindingで記述出来る。
// try?を各行に展開した書き方があるとすれば、これが該当するだろう。
// ただしOptional Bindingでは変数を省略したワンライナーにはできない。
func composeUsingTryOptionalAndOptionalBinding() -> Int? {
guard let a = try? foo().get(),
let b = try? bar(a).get(),
let c = try? hoge(b).get(),
let d = try? fuga(c).get(),
let e = try? piyo(d).get() else { return nil }
return e
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment