非同期プログラミングず蚈算匏

C5のasync / awaitに関する以前のメモ パヌトI 、 パヌトII で、Haskell、F、Nemerleなどの蚀語で同様のアプロヌチが実装されおいるこずを曞きたしたが、Cずは異なり、これらの蚀語は高レベルの抂念をサポヌトしおいたす。これにより、蚀語レベルではなくラむブラリずしおasync / awaitのスタむルで非同期蚈算を実装できたす。 Nemerleでは、このコンセプト自䜓がラむブラリずしお実装されおいるのは面癜いです。 この抂念の名前はモナドです。 非同期蚈算に加えお、モナドを䜿甚するず、リストの理解、継続、ダヌティな関数をクリヌンなブロックに倉換し、状態を暗黙的にドラッグするなど、他の倚くの利点を実装できたす。



䞀郚のモナドは、Cプログラマの「垌望」をyieldコレクションたたはyield foreachおよびlambda匏からyieldずしお実装したす。



この投皿の目的は、Nemerleを非同期プログラミングおよび蚈算匏に導入するこずですが、Fを孊習しおいる人にずっおも圹立぀可胜性があるため、Nemerleでの非同期プログラミングの実装は、Fに泚目しお行われたした。 䞀方、他の蚀語の問題であるいく぀かのタスク すべおの非同期呌び出しの埌 が、2、3行の蚈算匏を䜿甚しおどのように解決されるかは、誰かにずっお興味深いかもしれたせん。



おそらく、モナドずは䜕かを理解しおいる人は誰でも、モナドずは䜕かに぀いおの蚘事を曞いおいるでしょう。 私も䟋倖ではありたせんでした。 他の人がさらなるナレヌションを理解できる䞀方で、知っおいる人が芚えおいるように、できるだけ簡朔に説明するようにしたす。



モナド



モナドは、蚀語にサポヌトが組み蟌たれおいる生成パタヌンです。 次のペアがこのパタヌンの䞭心にありたす。



倚盞型



interface F<T> { ... }
      
      





圌に察するいく぀かの操䜜



 class M { static public F<T> Return<T>(T obj) { ... } static F<U> Bind<T,U>(F<T> obj, Func<T, F<U>> fun) { ... } }
      
      





Returnを䜿甚するず、モナド内の任意の倀を「ラップ」し、バむンドしおその䞊で倉換を実行できたす。 StringBuilderを䜿甚しお非垞に匱い類䌌性を匕き出すこずができたす。このコンストラクタヌには、コンストラクタヌパラメヌタヌで初期倀を枡し、Append *メ゜ッドで倉曎したす。



FをIEnumerableで眮き換える堎合、バむンド眲名はLinq SelectMany眲名に䌌おいたす。 これは驚くべきこずではありたせん。ずいうのも、Linqも玠晎らしい予玄をしおいるので、モナドだからです。 ちなみに、PDC2010では、バヌトデスメットが「LINQ、Take Two-Realizing the LINQ to Everything Dream」ずいうレポヌトで興味深い話をしたした リンク 。



Linqがモナドの堎合、単玔なLinq匏を䜜成しおみおください。



 var nums = Enumerable.Range(-2, 7); var sqrt = from n in nums where n >=0 select Math.Sqrt(n);
      
      





M操䜜を䜿甚したす。 最初に、M操䜜を宣蚀したす。



 static class MList { static public IEnumerable<T> Return<T>(T obj) { var list = new List<T>(); list.Add(obj); return list; } static public IEnumerable<U> Bind<T, U>(this IEnumerable<T> obj, Func<T, IEnumerable<U>> fun) { return obj.SelectMany(fun); } static public IEnumerable<T> Empty<T>() { return Enumerable.Empty<T>(); } }
      
      





そしお、Linq匏を曞き盎したす



 var nums = Enumerable.Range(-2, 7); var sqrt = nums .Bind(n => n >= 0 ? MList.Return(n) : MList.Empty<int>()) .Bind(n => MList.Return(Math.Sqrt(n)));
      
      





Linqの堎合よりもさらに悪化したしたが、これはモナドサポヌトがCに組み蟌たれおいないためです。 Nemerleの堎合、このコヌドは次のようになり、M操䜜を宣蚀したす。



 class MList { public Return[T](obj : T) : IEnumerable[T] { def data = List(); data.Add(obj); data } public Bind[T, U](obj : IEnumerable[T], f : T->IEnumerable[U]) : IEnumerable[U] { obj.SelectMany(f) } public Empty[T]() : IEnumerable[T] { Enumerable.Empty() } }
      
      





そしお、Linq匏を曞き盎したす



 def mlist = MList(); def nums = Enumerable.Range(-2, 7); def sqrt = comp mlist { defcomp n = nums; defcomp n = if (n >= 0) mlist.Return(n) else mlist.Empty(); return Math.Sqrt(n :> double); };
      
      





最初に、NemerleのdefはCのvarの移怍性のない類䌌物であり、ifは䞉項挔算子:)であり、コンストラクタヌの呌び出しにはnewは必芁ないこずを思い出しおください。 これたでのずころ、comp挔算子はモナド蚈算の開始をアナりンスし、次のパラメヌタヌはM操䜜を提䟛し、蚈算自䜓が続行されたす。



Linqず比范するず、1行ではなく3行ですが、これらの行は1぀の倉数で機胜する通垞のコヌドのように芋え、実際には元のコレクションから新しいコレクションを生成したす。 この䟋は教育目的で提䟛されおいたす。以䞋は、通垞のコヌドでは繰り返すのが非垞に難しい䟋です。 仕組みを芋おみたしょう。



defcompは、モナドこの堎合、IEnumerable [T]型をT型の倀に「倉換」し、逆に倀をモナドに倉換するマゞックオペレヌタヌです。 実際、魔法はなく、ただの衚珟です



 defcomp n = nums; ...
      
      





コンパむラによっお拡匵された



 mlist.Bind(nums, n => ...)
      
      





蚈算匏



Haskell蚀語に぀いお議論しおいるのであれば、モナドに぀いおの話はこれで終わりたす。 しかし、ハむブリッド蚀語関数型/呜什型の堎合、条件挔算子、ルヌプ、およびyieldなどの制埡構造が存圚するため、状況はもう少し耇雑です。 これがどのように問題を匕き起こすかを理解するために、M挔算を通るルヌプずルヌプ内のdefcomp挔算子を含むモナド蚈算を衚珟するこずができたす。



この問題の解決法は非垞に簡単です。たずえば、ブランチ挔算子ずルヌプの倉換を凊理するM操䜜メ゜ッドのセットに远加する必芁がありたすが、Wh​​ileには次のシグネチャがありたす。



 public F<FakeVoid> While<T>(Func<bool> cond, Func<F<FakeVoid>> body)
      
      





コンパむラが本䜓にモナド挔算子を含むルヌプを怜出するず、最初にルヌプ本䜓をバむンドチェヌンに倉換したす。BindはF <T>を返すため、このチェヌンはラムダ "=> body"でラップできたす。 Func <F <T >>の堎合、コンパむラヌはルヌプ条件をラムダでラップし、これらのラムダをM操䜜のWhileに枡したす。



各M操䜜はモナドを返す必芁がありたすが、ルヌプは䜕も返したせん。したがっお、モナドにラップできる倀はありたせん。 これらの目的のために、FakeVoidタむプのシングルトンが䜿甚されたす。



これで、蚈算匏の非公匏な説明ができたす。これは、呜什型蚀語のモナドです。 a-la haskellの堎合、コンパむラヌはdefcompを曞き換えお、単項蚈算の内郚で返すだけです。呜什型蚀語の堎合に既に述べたように、制埡構造も曞き換えられたす。以䞋の衚には、曞き換えられるすべおの挔算子がありたす。

defcomp 意味でモナドを拡匵し、割り圓おに近い意味
callcomp 倀が重芁でないずきに䜿甚されるモナドを展開したす
åž°ã‚‹ 匕数をモナドにラップし、モナド蚈算のブロックの最埌で䜿甚されたす。意味は関数から戻るこずに近い
returncomp 匕数はモナドであり、モナド蚈算のブロックの結果ずしおこのモナドを返したす。戻りずは異なり、再床ラップしたせん
利回り 匕数をモナドにラップし、return returnに䌌たアクションを実行したす
yieldcomp 匕数-モナド、yieldが匕数を再床ラップしないのずは異なり、yield returnず同様のアクションを実行したす
if、when、unless、while、do、foreach、for、using、try ... catch ... finally 通垞の制埡構造の単項バヌゞョン


Mオペレヌションのプロバむダヌに぀いおもう少し説明したす。 公匏には、それらはビルダヌず呌ばれ、蚈算匏を䜜成するずきにアヒルの型付けが䜿甚されたす。぀たり、ビルダヌはコンパむラヌが䜿甚するメ゜ッドを含む必芁がありたすが、ビルダヌはむンタヌフェむスを実装する必芁はありたせん。 この゜リュヌションを䜿甚するず、蚈算匏ですべおの機胜を䜿甚する予定がない堎合に、ビルダヌを郚分的に実装できたす。 ずころで、MListビルダヌを䜜成するずきにこのアプロヌチを既に䜿甚したしたdefcompずreturnのサポヌトのみが実装されおいたす。



むンタヌフェむスが䜿甚されないもう1぀の理由は、M操䜜の眲名により厳しい条件を課すこずです。 タむプごずのモナドずM操䜜の互換性のみが必芁です。 たずえば、䞊蚘の䟋では、モナドには1぀のゞェネリックパラメヌタヌがあるず想定されおいたしたが、いく぀かのパラメヌタヌを簡単に持぀こずができたす。さらにナレヌションを付けるこずは重芁ではありたせんが、これはContinuationモナドの䟋を䜿甚しお調べるこずができたす。



独自のビルダヌを䜜成する堎合は、 蚈算匏の゜ヌスを調べるこずをお勧めしたす 。



暙準ビルダヌの䟋





暙準ビルダヌは蚈算匏ラむブラリに組み蟌たれおいるため、むンスタンスを䜜成しおcompをパラメヌタヌずしお枡す代わりに、名前をパラメヌタヌずしお枡すだけで十分です。



䞀芧


リストビルダヌは、蚀語の暙準制埡構成䜓、およびyieldおよびyieldcompをサポヌトしたす。 リスト[T]Nemerleの暙準リストをモナドずしお䜿甚したす。 このビルダヌは、Cプログラマヌの2぀の長幎の「りィッシュリスト」を実装しおいるずいう点で興味深いものです。lambdafrom lambdaずyieldコレクションです。 最初に、蚘事の最初からのLinqク゚リアナログを芋おみたしょう。



 def num = Enumerable.Range(-2, 7); def sqrt : list[double] = comp list { foreach(n in num) when(n >= 0) yield Math.Sqrt(n); }
      
      





ご芧のずおり、リストビルダヌを䜿甚するず、関数を宣蚀するこずなくyield匏を䜿甚できたす。たた、オブゞェクトぞのリンクの代わりに䜿甚するこずもできたす。 このコヌドは、同等のlinq匏よりも読みやすいず思われたす。



次に、別の「りィッシュリスト」を怜蚎したす。コレクションを生成したす。最初にシヌケンスを生成するロヌカル関数を宣蚀し、それを2回呌び出しおコレクションを生成したす。



 def upTo(n) { comp list { for(mutable i=0;i<n;i++) yield i } } def twice = comp list { repeat(2) yieldcomp upTo(3); } Console.WriteLine(twice); //[0, 1, 2, 0, 1, 2]
      
      





私は自分のゞェネレヌタヌを曞く必芁があり、型のために「Enumerable.Range0、3」を䜿甚したせんでしたyieldcompはモナドが入力されるこずを期埅し、この堎合の型はリスト[int]、および「Enumerable.Range0、3 「IEnumerable [int]を返したす。 この矛盟を克服するために、列挙可胜な別のビルダヌがありたす。



列挙可胜


このビルダヌは、Listビルダヌをほが繰り返し、モナドのタむプずしおIEnumerable [T]のみを䜿甚し、無限シヌケンスを構築できたす。 最埌の䟋を曞き盎したす。



 def twice = comp enumerable { repeat(2) yieldcomp Enumerable.Range(0, 3); } foreach(item in twice) Console.Write($"$item "); //0, 1, 2, 0, 1, 2
      
      





配列


listおよびenumerableず同様に、配列は動䜜し、モナドのタむプずしお配列[T]のみを䜿甚したす。



非同期


最も耇雑ですが非垞に䟿利なビルダヌは、倚くの点でCの将来の非同期/埅機に䌌おいたす。 既存の非同期コンピュヌティングを組み合わせお、非同期コンピュヌティングを構築するために䜿甚されたす。



yieldずyieldcompを陀くすべおの操䜜をサポヌトしたす。



このビルダヌのモナド型はAsync [T]です。 このタむプのオブゞェクトは非同期蚈算を蚘述し、その結果はタむプTの倀になりたすCのTask <T>など。非同期操䜜が倀を返さない堎合、Tの代わりに特定のFakeVoidタむプが䜿甚されたす。 バむンド操䜜、そのタむプAsync [T] *T-> Async [U]-> Async [U]は、関数によるAsync [T]タむプの非同期蚈算を「継続」し、この関数はタむプTのオブゞェクトを入力結果非同期蚈算、Async [U]タむプの新しい非同期蚈算を返したす。



もう1぀のキヌタむプは抜象クラスExecutionContextです。その子孫のむンスタンスは、非同期操䜜たずえば、珟圚のスレッド、ThreadPoolのスレッド、たたはSynchronizationContextの䜿甚の開始を担圓したす。眲名は次のずおりです。



 public abstract class ExecutionContext { public abstract Execute(computatuion : void -> void) : void; }
      
      





非同期操䜜を開始するには、非同期操䜜を蚘述するオブゞェクトのStartメ゜ッドクラスAsync [T]を呌び出しお、ExecutionContext型のオブゞェクトを枡す必芁がありたす。メ゜ッドが匕数なしで呌び出された堎合、非同期操䜜はThreadPool.QueueUserWorkItemを䜿甚しお開始されたす。



Cで非同期/埅機実装を䜿甚できる拡匵機胜非同期CTPには、既存のクラスを非同期操䜜で補完する倚くの拡匵メ゜ッドが既にありたす。 非同期は、モナド実装を䜿甚しおラむブラリにそのような拡匵機胜を提䟛したせんが、既存のプリミティブに基づいおそれらを構築する簡単な方法を提䟛したす。 たずえば、フレヌムワヌクの最初のバヌゞョンから存圚するリク゚ストを非同期的に実行する既存のHttpWebRequest眲名の䞀郚を考えおみたしょう。



 public class HttpWebRequest : WebRequest, ISerializable { public override IAsyncResult BeginGetResponse(AsyncCallback callback, object state); public override WebResponse EndGetResponse(IAsyncResult asyncResult); }
      
      





次に、これらのプリミティブを䜿甚したモナドコンピュヌティングでの䜿甚に適した非同期拡匵を䜜成したす。



 public module AsyncExtentions { public GetResponseAsync(this request : HttpWebRequest) : Async[WebResponse] { Async.FromBeginEnd(request.BeginGetResponse(_, null), request.EndGetResponse(_)) } }
      
      





Nemerleの_は特殊文字であり、この堎合はカリヌ化が䜿甚されるこずを思い出しおください衚蚘f_はx => fxず同等です。 同様に、暙準の非同期蚈算甚のラッパヌを䜜成できたす。



NemerleのC101非同期サンプルから䜕かを曞きたしょう。たずえば、耇数のWebペヌゞを䞊行しお読み蟌み、タむトルを印刷したす。GetHtmlおよびGetTitle拡匵コヌドを省略したした。蚘事は既にドラッグされおいたす。



 public PrintTitles() : Async[FakeVoid] { comp async { def response1 = HttpWebRequest.Create("http://www.ya.ru").GetResponseAsync(); def response2 = HttpWebRequest.Create("http://www.habr.ru").GetResponseAsync(); defcomp response1 = response1; defcomp response2 = response2; Console.WriteLine(response1.GetHtml().GetTitle()); Console.WriteLine(response2.GetHtml().GetTitle()); } }
      
      





最初の2行では、非同期ペヌゞ読み蟌み操䜜が開始され、これらのメ゜ッドは実行時に非同期操䜜を蚘述するオブゞェクトを返したす。コンパむラの芳点からは、これらのオブゞェクトのタむプはAsync [WebResponce]monadです。 次の2行では、意味のモナドが拡匵されたす。意味の別のレベルでは、結果の期埅を意味したす。 最埌の行では、結果が凊理されたす。



おもしろいこずに、JavaScriptで行う正しい方法 すべおの非同期呌び出しの結果を埅぀に぀いおの議論が非垞に暑かったこずがわかりたした。お気に入り90、コメント100です。 しかし、䟋に戻りたしょう。



芚えおおくべき䞻なこずは、モナドは生成パタヌンであり、非同期蚈算を蚘述する関数を䜜成したが、それを開始せず、PrintTitles。Start。GetResultのように実行できるこずです。 実際、これは非垞に重芁です。゚ラヌの原因になる可胜性があるため、メ゜ッドがAsync [T]を返す堎合、このコヌドが蚈算を開始するのか、それを構築するだけなのかを認識する必芁がありたす 。 区別するために、おそらく呜名芏則を䜿甚する䟡倀がありたす。たずえば、コンピュヌティングを開始するメ゜ッドには非同期サフィックスが必芁です。



Cのasync / awaitに関する2番目の蚘事で、awaitは非同期蚈算を開始したスレッドのSynchronizationContextで非同期蚈算の結果の凊理を開始するこずを曞きたした。 Nemerleはこの点に関しお非垞に柔軟性があり、スレッド間で蚈算を転送するこずができたす。 ボタンクリックハンドラヌを考えたす。



 private button1_Click (sender : object, e : System.EventArgs) : void { def formContext = SystemExecutionContexts.FromCurrentSynchronizationContext(); def task = comp async { Thread.Sleep(5000); callcomp Async.SwitchTo(formContext); label1.Text = "success"; } _ = task.Start(SystemExecutionContexts.ThreadPool()); }
      
      





たず、珟圚のSynchronizationContextで蚈算を開始するExecutionContextを取埗し、次に非同期操䜜を蚘述したすThread.Sleepは重い蚈算を゚ミュレヌトし、実行コンテキストをスレッドの実行コンテキストguiに切り替えお結果を衚瀺したす。 蚈算自䜓は、ExecutionContextsスレッドプヌルで起動されたす。



それは魔法のように芋えたすが、実際にはすべおがすでに起こっおおり、その意味が重芁でない堎合、callcompは単にモナドを明らかにしたす。 しかし、なぜそれを開瀺するのでしょうか それは副䜜甚ず状態の問題であり、モナドの操䜜䞭に状態がそれらを介しおドラッグされ、オヌプン時のモナドはこの状態にアクセスし、それを倉曎するこずができたす。 この䟋では、状態はコヌドを実行するコンテキストの情報を保存し、この情報が倉曎されるず、新しいコンテキストに切り替わりたす。 詳现に぀いおは、 ゜ヌスを読むこずをお勧めしたす。興味深いです。



Async.SwitchToに加えお、実行のフロヌに圱響を䞎える他の興味深いモナドがありたす。たずえば、Async.Yieldは、実行コンテキストは倉曎されたせんが、実行コンテキストが倉曎されたこずを瀺したす。 堎合によっおは、これは䜕も行いたせん。ThreadPoolが䜿甚された堎合、このアクションはプヌルから別のスレッドぞのゞャンプを匕き起こしたす。



おわりに



結論ずしお、私はモナドが非垞に豊富なトピックであるこずにのみ泚意するこずができたす。 この蚘事では、State、Cont続き、Maybe別のCファンボヌむりィッシュリストなどの叀兞的なモナドには觊れたせんでした。 それらに぀いおは他の蚘事で読むこずができたす。私は実甚的な説明をしようずしたした。おかげでNemerleで非同期プログラミングずリスト/列挙可胜なモナドを䜿い始め、内郚で䜕が起こっおいるのかを知るこずができたす。



倚くの点で、将来のCでのawait / asyncの実装ずNemerleでの非同期プログラミングぞのモナドのアプロヌチは䌌おいたすが、await / asyncをサポヌトするための泚意点が1぀ありたす。次のバヌゞョンの蚀語が必芁です。蚀語ではなく蚀語。



私は質問にコメントしお答えおうれしいです。



All Articles