Swift Digest
Blog | Swift.org Blog

Swift Async Algorithms の発表

Introducing Swift Async Algorithms

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら

この記事の要点

何が発表されたのか

Swift Async Algorithms は、AsyncSequence を対象としたアルゴリズム群を提供するパッケージです。Swift が進めてきた、安全・シンプル・高性能な非同期プログラミングの取り組みの一環として位置づけられています。

このパッケージは、時間とともに流れる値 を扱うアルゴリズムを対象とします。debouncethrottle のように主に 時間 を扱うものだけでなく、combineLatestmerge のように値の 順序 を扱うものも含みます。zipSequence に対して行うような、複数の入力を扱う操作は、微妙な挙動や考慮すべきエッジケースが多く、実装が驚くほど複雑になりがちです。共有パッケージとしてこうした細部を正しく作り込み、十分なテストとドキュメントを備えることで、すべての Swift アプリがその恩恵を受けられるようにすることが狙いです。

土台となるのは Swift 5.5 で導入された AsyncSequence です。Swift 5.5 では、AsyncSequence の値を自然な for/in ループと await で処理できるようになり、mapfilter のような Sequence 相当の API も使えます。structured concurrency によって、中間状態を単なるローカル変数として扱い、throw する関数に直接 try を使い、非同期コードのロジックを同期コードと同じように書けるようになりました。

開発と API 設計は GitHubSwift フォーラム 上で行われます。パッケージとして提供することで、プラットフォームや OS バージョンをまたいだ柔軟なデプロイが可能になります。

何に使えるのか

パッケージには、おなじみのアルゴリズムの AsyncSequence 版として、zipcombineLatestmergechainbufferdebouncethrottle などが含まれます。

複数の AsyncSequence を合成する

zipSequence 版と同じく、2 つの異なる AsyncSequence の値からタプルを作ります。

for await (number, letter) in zip(numbers, letters) {
    print(number, letter)
}

AsyncSequencerethrows をサポートしており、エラー処理はほかの Swift コードと同じく try を使うだけです。

do {
    for try await (number, letter) in zip(numbers, lettersWithErrors) {
        print(number, letter)
    }
} catch {
    // Handle error
}

combineLatestmerge も複数の AsyncSequence の出力を合成しますが、出力の型やタイミングの制御の仕方がそれぞれ異なります。

時間を扱う

SequenceAsyncSequence の根本的な違いは、時間 という変数が加わる点です。ClockDuration を標準化する Proposal を土台として、このパッケージは debouncethrottle といったアルゴリズムを追加します。速すぎる間隔で到着する値を捨てる、といったよくある操作を手軽に実現できます。

for await value in input.debounce(for: .seconds(0.5)) {
    // Handle input, at most once per 0.5 seconds.
}

値の収集と同期シーケンスとの連携

有限の非同期シーケンスのすべての値を集めたい場合、1 行で書ける初期化子が用意されています。

let result = await Array(input)

async 関数を使うと、同期的な SequenceAsyncSequence と組み合わせられます。次の例では chain 関数と併用して、ファイルの内容の前に前置きを付け加えています。

let preamble = [
    "// This source file is part of the Swift.org open source project",
    "//",
    ""
].async

let lines = chain(preamble, URL(fileURLWithPath: "/tmp/Sample.swift").lines)

for try await line in lines {
    print(line)
}

Combine との関係

Apple は iOS 13・macOS 10.15 の SDK で Combine フレームワークを導入しました。Combine の API は PublisherSubscriber というインターフェイスを基盤とし、両者をつなぐ 演算子(operator) を組み合わせます。データが一方から他方へ流れる間に変換していく演算子の連鎖を宣言的に記述する設計です。

この設計では中間状態を異なる形で捉える必要があり、特に単一の値・エラー・共有が必要なデータを扱うとき、呼び出し側のコードが想像以上に複雑になることがあります。async/await の structured concurrency は、この種のロジックを表現する新しい方法を提供します。変換の連鎖ではなく、小さな部品に分割され上から下へ読める非同期コードとして書けるようになります。

Swift Async Algorithms は、Combine の実運用から得た教訓を取り入れつつ、Swift の structured concurrency を活かすパッケージとして設計されています。

導入・今後の位置づけ

発表時点で示されていた今後の構想であり、実現を約束するものではありません。

発表時点で公開されたのはプロトタイプ版です。まず動作する実装でプロジェクトを始動させ、その後 Swift フォーラムで詳細な設計議論を進めていく方針が示されました。次のような形でのコミュニティの参加が呼びかけられています。

バグ・機能要望・着手しやすいタスクの管理には GitHub Issues が使われます。async/awaitAsyncSequence がもたらす可能性を活かし、この領域のより高レベルな API の将来の開発と evolution を探っていく場として位置づけられています。

関連リンク