継続を利用してAsyncコンピュテーション式を実装できるか試す

Combine Deep Dives - ぐるぐる~ という記事にこういう文章があります。

このあたりを解決するために、Stateを使ったり継続を使ったりできるかもしれませんが、Async では未検証です。

そういえば検証したことはなかったなと思ったので、試してみました。

コード

コンパイルは通るしテストも通る、という代物は下記コードです。 ただし、テストは通ったけれど本当に非同期に動いているかどうかまでは検証していないというレベルです。

https://github.com/pocketberserker/ComputationExpressions/blob/b4000a3beddfd8d4b5793f0037222c13a4e96b15/src/ComputationExpressions/Async.fs

Option、Listの実装と異なる部分

OptionListでの実装は、パターンマッチによって値を分解することで包まれていた値を取り出すことができるのでわりと簡単に実装できていました。

しかしAsyncはパターンマッチで分解できません。 というわけで、とりあえず各メソッドでは関数をAsyncで返すようにします。 そしてRunメソッドでは値を返したいのでasync.Returnを渡します。

ゼロ値の処遇

今回実装したAsyncBuilderではZeroメソッドを提供せず、AsyncWithZeroBuilderゼロ値を渡すようにしました。 が、既存のコンピュテーション式よりもAsyncBuilderの表現力が落ちるためちょっと納得いってません。

本当は「ReturnReturnFromが呼び出されなければコンパイルエラー」というビルダークラスを作りたかったのですが、if then ceというコンピュテーション式の変形においてceZeroの型が一致しなければならず、そこを通過しつつRunメソッドのみコンパイルエラーになるような実装をついに導き出せませんでした…無念。

未検証の作戦として「Runで返す値をAsync<'T -> Async<'U>>のままにする」というものがあります。しかし、これはこれでAsync.RunSynchronousAsync.Startなどの多くのメソッドをラップする必要があるので悩みどころです。

まとめ

コンピュテーション式について考えると休日が吹き飛ぶの、つらい。