matarillo.com

The best days are ahead of us.

9.7 F# 1.0 -- 機能コアの改善:async/await

2021-09-25 00:00:21

2007年4月に、私はMartin Oderskyと共にローザンヌのEPFLで6週間のサバティカルを過ごしました。 Oderskyは当時Scalaを開発していましたから、言語とグループには刺激的な時期でした。 その時の訪問の中で、もう1つの重要なアイディアが種をまかれ、最終的にはコアF# 設計に追加されることになりました。 それは、コンピュテーション式 およびその 非同期プログラミング への応用です。 それは数年後のC# に影響を及ぼし、C# 5.0(async/await)および8.0(非同期シーケンス)になり、さらに他の多くの言語にも影響を及ぼすことになりました。

コンピュテーション式と “async” で解決した問題は以下の通りです。 まず、2003年以降、コモディティであるコンピューティングシステムにおけるマルチコアおよび並列処理への注目が高まっていました。 そして、Webプログラミングの台頭により、サーバー側での同時実行性と、クライアント側で長時間実行されるWeb要求が重要視されるようになっていました。 さらに、Windowsのコンテキストでは、「OSのスレッドは高価である」とみなせたため、純粋にOSのスレッドに基づく並行性へのアプローチは除外されました。 このような要因の組み合わせにより、言語とフレームワークは並行性とユーザーレベルスレッドについて強い関心を持つようになりました。 .NETでは、当時のC# の仕事の焦点は共有メモリーのプリミティブとロックでした[Duffy 2008]。 それらは低レベルで高性能なプリミティブには向いていましたが、.NETコミュニティはより良く、より生産的な抽象化を熱望していました。 他の多くの言語では、アクターのようなメッセージキューイングシステムや、先物(Future)や、継続に焦点が置かれていました。

理論的な側面からは、昔からこのように認識されていました:

  1. 非同期とは、継続渡しを介して実装されたモナディックプログラミング[Wadler 1995]の一種です。
  2. モナディック計算に「構文糖」を追加すると、プログラミング言語または仕様言語に表現力豊かな追加が行われるでしょう[Hudak et al. 2007]。

2007年のEPFLで、私はPhilipp Hallerがメッセージ処理のための react {…} 構成体をScalaに追加したことに気付き[Haller and Odersky 2009]、 非同期プログラミングを扱うためのある種の原始的な構成体がF# もで必要になるだろうと気づきました。 C# は、C# 2.0に「イテレーター」を追加していました(これもMSRによって開始されたものです)。 これは、非同期プログラミングの文脈でも興味深い、制御フローの暗黙の反転を特徴としていました。

非同期プログラミングに関するアイディアは、特に2001年にFabrice le Fessantによって開始されたよるシステムであるMLDonkey[Wikipedia 2019c]で使用されている非同期実装を通じて、OCamlコミュニティとそのメーリングリストにも広がっていました。 Haskellはモナディックシンタックス1を追加していました。 しかし、正格な関数型言語には、すべての制御構成体を非同期形式で再解釈することを可能にする適切で拡張可能な構文を備えたものがなく、 OCamlおよびStandard MLでは、関数コンビネーターのライブラリーが代わりに使用されることが一般的でした。

EPFLから戻った後、私は2007年5月頃にMargetsonとこれらの問題について議論しました。 彼は非同期プログラミングの実装におけるモナドの重要性を強調しました。 これにより、私はついにF# にモナド構文を追加して非同期プログラミングに適用することを試みることとなりました。 そしてそれは、2007年にF#に async {...} を追加することにつながりました[Syme et al. 2011]。 2007年10月10日に、私たちはこれらの機能をブログ記事「F# の非同期ワークフロー」[Syme 2007b]で発表しました。 当時のブログ記事で使用されていた、非同期コードの代表的なサンプルは次のとおりです。

let task1 = async { return 10+10 }
let task2 = async { return 20+20 }
Async.Run (Async.Parallel [ task1; task2 ])

ここでは:

  • async { return 10+10 }Async<int> 型のオブジェクトを生成します。
  • Async.Parallel [ task1; task2 ] は2つのタスク仕様を合成し、 Async<int[]> 型の新しい値を生成します。
  • Async.Run はこれを受け取って実行し、配列 [20; 40] を返します。

async { ... } という記法は非同期式を表します。そのような式の中では let! 構文のような式を使うことができ、次のようなコードは:

async {
    let! x = p1
    let! y = p2
    let z = x + y
    return z + y
}

以下のように解釈されます。

async.Bind(p1, (fun x ->
    async.Bind(p2, (fun y ->
        let z = x + y in
        async.Return(z + y))))

ここで、「非同期」プログラミング以外にも適するようにこの記法のメカニズムを一般化できないだろうかという疑問が起こりました。そして、F# ではその一般化を コンピュテーション式 と呼びます。


インデックスへ戻る


  1. Haskellにモナディックな do 記法が追加されたのは、1994年にMark JonesがGoferに追加したことに由来します。Mark Jones氏の影響は[Launchbury 1993]にクレジットされています。 ↩︎