プロポーザルAsync/Await for Swiftの解説と感想

# はじめに

最近Swift用のasync/await本を書いていまして、その発端ともなった、クリス・ラトナーさんのプロポーザル`Async/Await for Swift`の下書きについて解説を加えているのですが、なんかもう、そのモチベーションが全然保てないわけです。一度ざっくりと理解してしまったことについて解説するのはしんどい。もっとよくわからん段階で解説文を書いておけばよかったなあと思っています。

解説するモチベーションの低下、そしてそもそも正しく理解できてるかもわからないし、解説すること必要？っていう話と、そしてその解説方法って感想が入りまくってて読みづらくない？と思っていてもやっているので、その一部分を公開しようと思ってます（なので、文中に「本書」などと出てきたりするのはこの文章が本の一部だからです）。

プロポーザルの下書きはgistで公開されています。

Async/Await for Swift
https://gist.github.com/lattner/429b9070918248274f25b714dcfc7619

この文章ではリビジョン [4 Sep 2017](https://gist.github.com/lattner/429b9070918248274f25b714dcfc7619/revisions)を元にしています。

解説する項目は前半の項目です

- Introduction
- Motivation: Completion handlers are suboptimal
- Proposed Solution: Coroutines

以降、箇条書きで翻訳したものとその解説を書いていきます。もし、俺はこう解釈したぜというご意見があれば、コメント欄でもtwitterでもその意見を教えていただければと思います。

# Async/Await for Swift

## Introduction

- モダンCocoa開発にはクロージャとコンプリートハンドラを使用した多くの非同期プログラミングが必要だがこれらのAPIは使いにくい
    - 何が問題？
        - 複数の非同期操作が使用されている場合
        - エラー処理が必要な場合
        - 非同期呼び出し感の制御フローが複雑になる場合
- このプロポーザルはこれをより自然で、間違いを起こりにくくする
- このプロポーザルではSwiftのコルーチンモデルを紹介する
    - 関数を`async`にすることで
        - プログラマは非同期操作を含む複雑なロジックを作成し
        - コンパイラはそのロジックを実装するために必要なクロージャとステートマシンを生成します
- 重要なことは
    - 完全に同時実行時ランタイムに依存しないコンパイラのサポートを提案していることを理解すること
- 新しいランタイムモデル（`Actor`など）は含まれていない
- pthreadや他のAPIと同じようにGCDでも機能する
- 他の言語の設計とは異なり、`Future`や`channels`などには依存しない
  - ライブラリとして構築はできる
- 暗黙的に生成されたクロージャを変換および操作するためのコンパイラサポートロジックのみが必要
- このプロポーザルの提案者など
    - [Chris Lattner](https://github.com/lattner)さん
    - [Joe Groff](https://github.com/jckarter)さん
- インスピレーションを受けたプロポーザルのgist
    - [Oleg Andreev](https://github.com/oleganza)さんの [gist](https://gist.github.com/oleganza/7342ed829bddd86f740a)
    
##### 解説

コルーチンとは何かということについては、このプロポーザルで徐々に書かれています。しかし、このプロポーザルではSwiftのコンパイラレベルでサポートするコルーチンとは何かであって、読者としてはまず一般的なコルーチンとは何かをざっくりとでも知りたいことでしょう。

wikipediaから[コルーチンのページ](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3)について引用します

> コルーチン（英: co-routine）とはプログラミングの構造の一種。サブルーチンがエントリーからリターンまでを一つの処理単位とするのに対し、コルーチンはいったん処理を中断した後、続きから処理を再開できる。接頭辞 co は協調を意味するが、複数のコルーチンが中断・継続により協調動作を行うことによる。

> サブルーチンと異なり、状態管理を意識せずに行えるため、協調的処理、イテレータ、無限リスト、パイプなど、継続状況を持つプログラムが容易に記述できる。

> コルーチンはサブルーチンを一般化したものと考えられる。コルーチンをサポートする言語には Modula-2、Simula、Icon、Lua、C#、Limbo などがある。マルチスレッドで原理的には同じことができるため、現在はそちらが使われるケースが多い。これはマルチスレッドであれば直接OSの支援を受けられることや、エントリー/リターンの構造を変えずにコードを多重化できるので、過去の言語との親和性が良いなどが理由である。ただし、マルチスレッドの場合プログラマが同期制御を行わなければならないので、コルーチンのような簡易さはない。

> コルーチンという名称は、メルヴィン・コンウェイの1963年の論文が起源である。

以上を踏まえてプロポーザルに戻ると、「コンパイラはそのロジックを実装するために必要なクロージャとステートマシンを生成します」とあります。ステートマシンを生成するとは何か、どういうことかということについては、Kotlinでのコルーチンサポートが理解の手助けになってくれるでしょう。

Kotlinの`async/await`については id:sys1yagi さんのブログ記事 [Kotlin 1.1 async/awaitの仕組みと使い方の概要 for Android - visible true](http://sys1yagi.hatenablog.com/entry/2016/12/19/184352)が参考になります。

> Kotlin 1.1ではコルーチンの処理全体を「状態」の集まりとしてステートマシンに変換することで、JVM上での動作を実現しています。

> コルーチンのコードをステートマシンとして解釈するために、コルーチンの処理を表すcoroutineキーワードと、中断点を示すためのsuspendキーワードが追加されています。これらのキーワードによって「コルーチンの実装」を言語機能としてサポートします。

> Kotlin 1.1で提供されるasync/awaitやジェネレータは、コルーチンの実装としてライブラリの形式で提供されます。


つまり、このプロポーザルではKotlinと同じようにコルーチンの処理を「状態」として、その状態を処理ごとに分割し、コルーチンによる処理の停止や再開といった動作をさせるようにする、ということだと理解できます。

また、本書ではさまざまな言語で`async/await`には`Future`や`Promise`が使われているという話やその解説を行ってきましたが、`Future`は、このプロポーザルでは言語機能としてサポートしないということを述べているのが興味深いところです。

しかし、`Future`の必要性がないわけではなく、`Future`を実装するならどうするか、や`Future`をサポートしない理由もこのプロポーザルに含まれています。

## Motivation: Completion handlers are suboptimal

### Problem 1: Pyramid of doom

- 連続した処理はネストしたブロックで不自然に構成されてしまう
  - このPyramid of doomは実行中のコードを追跡するのを困難に
    - 他の効果も引き起こしてしまう
- Pyramid of doomの例は次の通り

```swift
func processImageData1(completionBlock: (result: Image) -> Void) {
    loadWebResource("dataprofile.txt") { dataResource in
        loadWebResource("imagedata.dat") { imageResource in
            decodeImage(dataResource, imageResource) { imageTmp in
                dewarpAndCleanupImage(imageTmp) { imageResult in
                    completionBlock(imageResult)
                }
            }
        }
    }
}

processImageData1 { image in
    display(image)
}
```

### Problem 2: Error handling

- エラーを処理することは困難で冗長になる
  - Swift 2では、同期コードのエラー処理モデル(`guard`)が導入された
  - コールバックベースのインターフェイスではその利点がない

```swift
func processImageData2(completionBlock: (result: Image?, error: Error?) -> Void) {
    loadWebResource("dataprofile.txt") { dataResource, error in
        guard let dataResource = dataResource else {
            completionBlock(nil, error)
            return
        }
        loadWebResource("imagedata.dat") { imageResource, error in
            guard let imageResource = imageResource else {
                completionBlock(nil, error)
                return
            }
            decodeImage(dataResource, imageResource) { imageTmp, error in
                guard let imageTmp = imageTmp else {
                    completionBlock(nil, error)
                    return
                }
                dewarpAndCleanupImage(imageTmp) { imageResult in
                    guard let imageResult = imageResult else {
                        completionBlock(nil, error)
                        return
                    }
                    completionBlock(imageResult)
                }
            }
        }
    }
}
processImageData2 { image, error in
    guard let image = image else {
        error("No image today")
        return
    }
    display(image)
}
```

##### 解説

`guard`を非同期処理のクロージャごとに呼び出して完了クロージャを呼び出すコード、みんなも書いたことあるよな。って感じ、それが`async/await`では必要なくなるよということですね。

### Problem 3: Conditional execution is hard and error-prone

- 条件によって非同期関数を実行することはかなり苦痛
  - これに対するおそらく最善の方法はヘルパーとしてクロージャを定義して書いておく
      - 下記がその例

```swift
func processImageData3(recipient: Person, completionBlock: (result: Image) -> Void) {
    let continuation: (contents: image) -> Void = {
      // ... continue and call completionBlock eventually
    }
    if recipient.hasProfilePicture {
        continuation(recipient.profilePicture)
    } else {
        decodeImage { image in
            continuation(image)
        }
    }
}
```

### Problem 4: Many mistakes are easy to make

- blockを呼び出したあとにreturnしないと問題をデバッグするのは難しい
    - 下記がその例（しかし先回りして`guard`について書いてる）


```swift
func processImageData4(completionBlock: (result: Image?, error: Error?) -> Void) {
    loadWebResource("dataprofile.txt") { dataResource, error in
        guard let dataResource = dataResource else {
            return // <- forgot to call the block
        }
        loadWebResource("imagedata.dat") { imageResource, error in
            guard let imageResource = imageResource else {
                return // <- forgot to call the block
            }
            ...
        }
    }
}
```

- `guard` によって`return`忘れを保護してくれる
  - ただし、必ずしも適切かというとそうでもない
    - 下記がその例

```swift
func processImageData5(recipient:Person, completionBlock: (result: Image?, error: Error?) -> Void) {
    if recipient.hasProfilePicture {
        if let image = recipient.profilePicture {
            completionBlock(image) // <- forgot to return after calling the block
        }
    }
    ...
}
```

##### 解説

ここの部分、先に`guard`の話だしてるので分かりづらいですね。まず、`return`忘れたらつらいよねっていう話があって、そこから、`guard`が使えるときは`return`忘れようがないからそのときは話し別だよねっていう持っていきかたのほうが自然な気がします。

### Problem 5: Because completion handlers are awkward, too many APIs are defined synchronously

- 完了ハンドラを利用した非同期APIを定義して使用することでAPIをブロックすることができても
  - それは同期的動作で定義されていると考えている
    - UIアプリケーションで問題となるパフォーマンスと応答性の問題につながる可能性がある
    - 例えばサーバのように、非同期性がスケールを達成するために重要である場合には使用できないAPIの定義につながる可能性がある

##### 解説

ここはわかったようなわからんような感じで、具体例がほしいところです。APIをブロックするとは？パフォーマンスと応答性の問題とは具体的に？という感じですね。

### Problem 6: Other "resumable" computations are awkward to define

- 計算の再開について自乗のリストを生成するコードを書く場合を例に

```swift
for i in 1...10 {
    print(i*i)
}
```

- これを段階的に値を生成するなら
  - `AnyIterator`や`sequence(state:,next:)`がある
    - しかしそれらは命令形のような明快さと自明性がない
- 対照的に、ジェネレータ(`yield`)を持つ言語では、これに近いものを書くことができる

```swift
func getSequence() -> AnySequence<Int> {
    let seq = sequence {
        for i in 1...10 {
            yield(i*i)
        }
    }
    return AnySequence(seq)
}
```

##### 解説

ここで述べられている`yield`について解説すると、Kotlinのそれに近いものだと思います。例えば`AnySequence<Int>`が`next`メソッドを持っているとすると、その利用例からこの`yield`の動きは次のようになるものでしょう。

```swift
let sequence = getSequence()
sequence.next() // 1 * 1 が計算される
sequence.next() // 2 * 2 が計算される
sequence.next() // 3 * 3 が計算される
sequence.next() // 4 * 4 が計算される
sequence.next() // 5 * 5 が計算される
```

また、おそらく戻り値としてKotlinと同じようにyieldの結果が返されると考えられます。

```swift
let sequence = getSequence()
print(sequence.next()) // => 1
print(sequence.next()) // => 4
```

これはプロポーザルに書いてある内容ではなく、あくまで私の想像です（プロポーザルに`next`メソッドのようなことを書いてほしい）。

## Proposed Solution: Coroutines

- コルーチンの抽象化は問題解決のための標準的な方法
- コルーチンは
  - 関数が値を返すか、中断することを可能にする
  - 基本関数の拡張
  - ジェネレータ（`yield`）や非同期モデルその他の機能を実装するのに使用できる
- プロポーザルでは
  - 完了ハンドラの多くの問題を排除
  - 最も一般的なユースケースに向かって命名法と用語をバイアスする
- `async`か`yields`かはコアセマンティクスには関係しない
  - 末尾のalternate-syntax-optionsで言及する
- 提案するコルーチンモデルはインターフェースではない
  - コンプリートハンドラのシンタックスシュガーと考えることができる
  - コルーチンの導入によって完了ハンドラが呼び出されるキューは変更されない
 
### Async semantics

- 関数型は`throw`できる
- プロポーザルでは`async`できるように拡張する
  - そうなると関数型は次のようになる

```swift
   (Int) -> Int               // #1: Normal function
   (Int) throws -> Int        // #2: Throwing function
   (Int) async -> Int         // #3: Asynchronous function
   (Int) async throws -> Int  // #4: Asynchronous function, can also throw.
```

- 通常の関数#1が`throws`する際に#2に変換される
  - 同様に、#3は明示的に#4になる
- 関数宣言時の`async`キーワードは次の通り

```swift
func processImageData() async -> Image { ... }

// Semantically similar to this:
func processImageData(completionHandler: (result: Image) -> Void) { ... }
```

- `async`な関数は、コルーチンの中断をすることができる
- 明示的にするには`await`キーワードでマークする
  - プロポーザルに示した例は`async`と`await`で次のようになる

```swift
func loadWebResource(_ path: String) async -> Resource
func decodeImage(_ r1: Resource, _ r2: Resource) async -> Image
func dewarpAndCleanupImage(_ i : Image) async -> Image

func processImageData1() async -> Image {
  let dataResource  = await loadWebResource("dataprofile.txt")
  let imageResource = await loadWebResource("imagedata.dat")
  let imageTmp      = await decodeImage(dataResource, imageResource)
  let imageResult   =  await dewarpAndCleanupImage(imageTmp)
  return imageResult
}
```

- `await`関数が呼び出せる条件
  - `async`関数か別の（非同期？）クロージャから呼び出せる
- `do/catch`ブロックの内部にないと`throw`関数が呼び出せないのと同じ

##### 解説

ここらへんの訳と解釈についてはかなり自信がありません。原文を読むと、`async`関数を呼び出せる条件が書いてあって、`await`について書いてないように思えるんですが、文脈や`do/catch`の話から`await`の条件を書いているように思えます。

#### Entering and leaving async code

- 非同期コンテキストの入力と中断を可能にするためにいくつかのプリミティブが必要

```swift
func beginAsync(_ body: () async throws -> Void) rethrows -> Void

func suspendAsync<T>(
  _ body: (_ continuation: @escaping (T) -> ()) -> ()
) async -> T

func suspendAsync<T>(
  _ body: (_ continuation: @escaping (T) -> (),
           _ error: @escaping (Error) -> ()) -> ()
) async throws -> T
```

- beginAsync
  - 非同期コルーチンを開始し、制御を`body`に移動する
- suspendAsync
  - 現在の非同期タスクを一時停止し、タスクの継続終了とともに`body`を呼び出す
  - `continuation`を呼び出すとコルーチンを再開する
  - `continuation`が複数回呼び出されるのは致命的なエラー
- IBActionでの利用例としてまずは完了ハンドラでの例を示す

```swift
@IBAction func buttonDidClick(sender:AnyObject) {
  // 1
  processImage(completionHandler: {(image) in
    // 2
    imageView.image = image
  })
  // 3
}
```

- 完了ハンドラでの例では実行順序は次のようになる
  - #1
    - #3
      - #2
- 次に`beginAsync`を使った例を示す

```swift
@IBAction func buttonDidClick(sender:AnyObject) {
  // 1
  beginAsync {
    // 2
    let image = await processImage()
    imageView.image = image
  }
  // 3
}
```

- コールバックベースのAPIを非同期コルーチンAPIとしてラップすることを可能にする

```swift
// Legacy callback-based API
func getStuff(completion: (Stuff) -> Void) { ... }

// Swift wrapper
func getStuff() async -> Stuff {
  return await suspendAsync { continuation in
    getStuff(completion: continuation)
  }
}
```

- libdispatchやpthreadsなどの同時実行ライブラリの機能
  - コルーチンに優しい方法でも提供される

```swift
extension DispatchQueue {
  /// Move execution of the current coroutine synchronously onto this queue.
  func syncCoroutine() async -> Void {
    await suspendAsync { continuation in
      sync { continuation }
    }
  }

  /// Enqueue execution of the remainder of the current coroutine
  /// asynchronously onto this queue.
  func asyncCoroutine() async -> Void {
    await suspendAsync { continuation in
      async { continuation }
    }
  }
}

func queueHopping() async -> Void {
  doSomeStuff()
  await DispatchQueue.main.syncCoroutine()
  doSomeStuffOnMainThread()
  await backgroundQueue.asyncCoroutine()
  doSomeStuffInBackground()
}
```

- `func syncCoroutine() async -> Void `で現在のコルーチンの実行を同期してこのキューに移動する
  - 例ではメインキューで実行しているのでメインキューになる
- `func asyncCoroutine() async -> Void`で現在のコルーチンの残りの部分をこのキューに非同期的にエンキューする
  - 例ではバックグラウンドキューで実行しているので残りの処理がバックグラウンドキューにエンキューされる

（この例は分かりづらいし解釈に自信がありません）

- `Future`のようなコルーチンを調整するための一般化された抽象化も構築することができる
- `Future`は
  - まだ解決されていない未来の値を表す値
  - `Future`の正確なデザインはこの提案の対象外
    - 対象外だけど実装例を示す

（本書では`Future`の実装例を省略します）

- 示した`Future`の実装例はパフォーマンスとAPIの脆弱性がある
  - 抽象化を`async/await`の上にどのように構築するかのスケッチ
- `Future`はパラレル実行を可能にする
  - パラレルコールを個々の`Future`オブジェクトにラップする


```swift
func processImageData1a() async -> Image {
  let dataResource  = Future { await loadWebResource("dataprofile.txt") }
  let imageResource = Future { await loadWebResource("imagedata.dat") }

  // ... other stuff can go here to cover load latency...

  let imageTmp    = await decodeImage(dataResource.get(), imageResource.get())
  let imageResult = await dewarpAndCleanupImage(imageTmp)
  return imageResult
}
```

- この例では
  - 最初の２つの操作が順番に開始
  - 未評価の計算は`Future`の値にラップされる
  - （decodeImage）関数は画像をデコードする前にその完了を持つ

##### 解説

最初の例は処理を置き換えるというかレガシーなインターフェースを同期的に書き換えることができますよという例ですね


# おわりに

プロポーザルにはまだ残りがあるので、それについては日を改めたいですね...

- Conversion of imported Objective-C APIs
    - あんまり興味がないなあ
- Source Compatibility
    - まあそこまで興味ないなあ
- Effect on ABI stability
    - ABIについては`async/await`と関係ないよってのことが書いてある
- Alternate Syntax Options
- Alternatives Considered
- Potential Future Directions