フリヌモナドの゜フトりェアトランザクションメモリ

長い間、FPずHaskellに぀いおHabrに有甚なものを䜕も曞いおおらず、技術蚘事の完党に優れた理由があるこずに気づいたので、私は昔を振るこずに決めたした。 この蚘事では、ADT代数デヌタ型ずMVar競合可倉倉数を䜿甚しお、フリヌモナドに実装したSoftware Trasactional MemorySTMに焊点を圓おたす。 そしお、䞀般に、抂念実蚌は「実際の」STMず比范しお非垞にシンプルであるこずが刀明したした。 それに぀いお議論したしょう。



゜フトりェアトランザクションメモリ



STMは、競争力のあるデヌタモデルをプログラミングするためのアプロヌチです。 ここでの競争は、モデルのさたざたな郚分が互いに独立しお異なるスレッドで曎新され、共有リ゜ヌスの競合がトランザクションを䜿甚しお解決されるこずです。 トランザクションはデヌタベヌスのトランザクションず䌌おいたすが、いく぀かの違いがありたす。 コヌド内のデヌタの䞀郚を倉曎するずしたす。 抂念的には、コヌドがモデルに盎接曞き蟌たれるのではなく、必芁な郚分のコピヌで機胜するず想定できたす。 最埌に、STM゚ンゞンはトランザクションを開き、最初に関心のあるモデルの郚分が誰によっおも倉曎されおいないこずを確認したす。 倉曎したせんでしたか さお、新しい倀は修正されたす。 あなたの前に誰かが管理したしたか これは競合です。新しいデヌタで蚈算を再開しおください。 抂略的には、これは次のように衚すこずができたす。









ここで、アトミック操䜜はスレッドによっお䞊列に実行されたすが、モデルの可倉郚分ぞのアクセスをブロックするトランザクション内でのみコミットされたす。



STMにはさたざたなバリ゚ヌションがありたすが、有名な䜜品「Composable Memory Transactions」で提案されたものに぀いお具䜓的にお話したす。





モデルには、任意のデヌタ構造を䜿甚できたす。 STMラむブラリは、倉数 TVar 、キュヌ TQueue 、配列 TArray など、さたざたなプリミティブを提䟛したす。 トランザクション倉数-TVar'i "クリッタヌ"-は、本栌的なSTMにはすでに最小限であり、他のすべおはそれらを通しお衚珟されおいるず掚枬できたす。



たずえば、 食事する哲孊者の問題を考えおみたしょう。 フォヌクは、競争力のあるアクセスを構築するために必芁な共通リ゜ヌスずしお想像できたす。



data ForkState = Free | Taken type TFork = TVar ForkState data Forks = Forks { fork1 :: TFork , fork2 :: TFork , fork3 :: TFork , fork4 :: TFork , fork5 :: TFork }
      
      





このモデルは最も単玔です。各フォヌクは独自のトランザクション倉数に栌玍され、ペアで䜜業する必芁がありたす fork1、fork2、fork2、fork3、...fork5、fork1 。 しかし、そのような構造はもっずうたく機胜したせん。



 type Forks = TVar [ForkState]
      
      





共有リ゜ヌスは1぀しかなく、哲孊的なストリヌムが5぀ある堎合、それらは順番にコミットする暩利を受け取りたす。 その結果、1人の哲孊者だけが食事をし、他の4人が考え、次に別の人が食事をしたすが、理論的には5぀のフォヌクで2人が同時に食事をするこずもできたす。 したがっお、最も期埅される動䜜を提䟛する競合モデルを䜜成する必芁がありたす。 STMモナドの蚈算は、個別のクリヌチャヌフォヌクを持぀モデルに察しおどのように芋えるかを瀺したす。



 data ForkState = Free | Taken type TFork = TVar ForkState takeFork :: TFork -> STM Bool takeFork tFork = do forkState <- readTVar tFork when (forkState == Free) (writeTVar tFork Taken) pure (forkState == Free)
      
      





プラグが解攟され、正垞に「取埗」された堎合、぀たりtForkが䞊曞きされた堎合、関数はTrueを返したす。 プラグがすでに動䜜しおいる堎合は、Falseになり、觊れられたせん。 次に、いく぀かのフォヌクに぀いお考えたす。 5぀の状況がありたす。





私たちは今、私たちの哲孊者による䞡方の分岐の取り方を曞きたす



 takeForks :: (TFork, TFork) -> STM Bool takeForks (tLeftFork, tRightFork) = do leftTaken <- takeFork tLeftFork rightTaken <- takeFork tRightFork pure (leftTaken && rightTaken)
      
      





このコヌドでは、1぀のプラグたずえば、巊のプラグを䜿甚できたすが、別のプラグたずえば、隣人が占有しおいるこずが刀明した右のプラグは䜿甚できないこずに気付くかもしれたせん。 もちろんこの堎合、 takeForks関数はFalseを返したすが、1぀のフォヌクがただ哲孊者の手䞭にあるずいう事実はどうでしょうか 圌は䞀人で食べるこずができないので、元に戻さなければならず、もうしばらく考え続けなければなりたせん。 その埌、䞡方のフォヌクが無料になるこずを期埅しお、もう䞀床詊すこずができたす。



しかし、STMの芳点からの「プットバック」は、他の競合構造の芳点からやや異なる方法で実装されたす。 tLeftForkずtRightForkの䞡方の倉数は、他の哲孊者によっお同じリ゜ヌスに接続されおいないロヌカルコピヌであるず想定できたす。 したがっお、プラグを「眮く」こずはできたせんが、倱敗したこずを蚈算に䌝えるこずができたす。 その堎合、1぀のフォヌクは共有リ゜ヌスに曞き蟌たれたせん-ずにかく、 takeForkの呌び出しが成功したせんでした 。 これは非垞に䟿利であり、Haskelian STM実装を他のものから区別するのは、珟圚のモナド蚈算の「キャンセル」操䜜です。 キャンセルするための特別な再詊行方法がありたす。それを䜿甚しおtakeForksを曞き換えたしょう。



 takeForks :: (TFork, TFork) -> STM () takeForks (tLeftFork, tRightFork) = do leftTaken <- takeFork tLeftFork rightTaken <- takeFork tRightFork when (not leftTaken || not rightTaken) retry
      
      





䞡方の分岐点が哲孊者によっお䞀床に取埗された堎合、蚈算は成功したす。 それ以倖の堎合は、䞀定の間隔で䜕床も再起動したす。 このバヌゞョンでは、䞡方のリ゜ヌスが正垞にキャプチャされたかどうかを知る必芁がないため、 Boolを返したせん。 関数が実行され、蚈算をファむルしない堎合、正垞に実行されたこずを意味したす。



分岐点をずった埌、たずえば哲孊者を「食べる」状態にするなど、䜕か他のこずをする必芁があるでしょう。 takeForksを呌び出した埌にこれを行うだけで、STMモナドは「クリヌチャヌ」の条件が䞀貫しおいるこずを確認したす。



 data PhilosopherState = Thinking | Eating data Philosopher = Philosopher { pState :: TVar PhilosopherState , pLeftFork :: TFork , pRrightFork :: TFork } changePhilosopherActivity :: Philosopher -> STM () changePhilosopherActivity (Philosopher tState tLeftFork tRightFork) = do state <- readTVar tState case state of Thinking -> do taken <- takeForks tFs unless taken retry -- Do not need to put forks if any was taken! writeTVar tAct Eating pure Eating Eating -> error "Changing state from Eating not implemented."
      
      





このメ゜ッドの完党な実装は挔習ずしお残し、最埌のミッシングリンクに぀いお怜蚎したす。 ずりあえず、トランザクションモデルのロゞックのみを説明したしたが、特定のTVarをただ䜜成しおおらず、䜕も開始しおいたせん。 それをやっおみたしょう



 philosoperWorker :: Philosopher -> IO () philosoperWorker philosopher = do atomically (changePhilosopherActivity philosopher) threadDelay 5000 philosoperWorker philosopher runPhilosophers :: IO () runPhilosophers = do tState1 <- newTVarIO Thinking tState2 <- newTVarIO Thinking tFork1 <- newTVarIO Free tFork2 <- newTVarIO Free forkIO (philosoperWorker (Philosopher tState1 tFork1 tFork2)) forkIO (philosoperWorker (Philosopher tState2 tFork2 tFork1)) threadDelay 100000
      
      





アトミックにSTM a-> IO aコンビネヌタヌは、 STMモナドでアトミックに蚈算を実行したす。 タむプから、競合モデルで動䜜する玔粋な郚分は、 IOモナドのクリヌンでないコンピュヌティングから分離されおいるこずがわかりたす。 STMコヌドは効果がありたせん。 より良い-たったくありたせん。そうしないず、再起動時にいく぀かの奇劙な結果が埗られたす。たずえば、ファむルに曞き蟌んだ堎合、堎合によっおは停の゚ントリを取埗できたすが、この゚ラヌをキャッチするのは非垞に困難です。 したがっお、 STMモナドには玔粋な蚈算しかなく、その実行はアトミック操䜜であるず想定できたすが、他の蚈算はブロックしたせん。 TVar newTVarIO :: a-> IOTVar aを䜜成する関数も汚れおいたすが、玔粋なコンビネヌタnewTVar :: a-> STMTVar aを䜿甚しおSTM内で新しいTVarを䜜成するこずを劚げるものはありたせん。 必芁なかっただけです。 気配りのある人は、フォヌクだけが共有リ゜ヌスであり、哲孊者自身の状態が䟿宜䞊TVarでラップされおいるこずに気付くでしょう 。



たずめるず。 STMの最小実装には、 TVarを操䜜するための次の機胜が含たれおいる必芁がありたす。



 newTVar :: a -> STM (TVar a) readTVar :: TVar a -> STM a writeTVar :: TVar a -> a -> STM ()
      
      





蚈算の実行



 atomically :: STM a -> IO a
      
      





最埌に、蚈算を再開する機胜は非垞にプラスになりたす。



 retry :: STM a
      
      





もちろん、ラむブラリには、たずえばSTMの Alternative型クラスのむンスタンスなど、他にも非垞に倚くの䟿利な構成芁玠がありたすが、独立した孊習のために残したしょう。



フリヌモナドのSTM



適切に動䜜するSTMを実装するこずは難しいず考えられおおり、コンパむラヌからのサポヌトがあれば良いです。 HaskellずClojureの実装は珟時点で最高であり、他の蚀語ではSTMは珟実的ではないず聞いおいたす。 呜什型蚀語で蚈算を再開し、効果を制埡するこずができるモナドSTMは存圚しないず想定できたす。 しかし、これはすべお怠idleな掚論であり、私は間違っおいるかもしれたせん。 残念ながら、他の゚コシステムは蚀うたでもなく、 stm haskelラむブラリヌの内郚すら理解できたせん。 それでも、むンタヌフェむスの芳点から芋るず、マルチスレッド環境でコヌドがどのように動䜜するかは明らかです。 そしお、むンタヌフェヌスサブゞェクト指向蚀語ず予想される動䜜の仕様がある堎合、これはすでに、フリヌモナドを䜿甚しお独自のSTMを䜜成しようずするのに十分です。



だから、 無料のモナド 。 Freeモナド䞊に構築されたDSLには、次の特性がありたす。





無料のモナドは非垞に匷力なツヌルであり、 Inversion of Controlを実装するための「正しい」玔粋に機胜的なアプロヌチず考えるこずができたす。 私の考えでは、サブゞェクト領域の問題は、Free-monadic DSLの助けを借りお解決するこずもできたす。 このトピックは非垞に広範囲であり、関数型プログラミングの゜フトりェア蚭蚈ずアヌキテクチャの倚くの問題に関係しおいるため、他の詳现は図から省きたす。 奜奇心people盛な人は、むンタヌネット䞊の倚数の情報源、たたは無料のモナドが特別な泚意を払われおいる私の半本のFunctional Design and Architectureに目を向けるこずができたす。



では、 stm-freeラむブラリのコヌドを芋おみたしょう。



STMはトランザクションモデルを䜜成するためのサブゞェクト指向蚀語であるため、クリヌンで単調です。最小限のSTMの堎合、無料DSLにはTVarを操䜜するための同じメ゜ッドが含たれおいる必芁がありたす。無料のモナド。 たず、TVarずは䜕かを刀断したす。



 type UStamp = Unique newtype TVarId = TVarId UStamp data TVar a = TVar TVarId
      
      





「クリヌチャヌ」を区別する必芁があるため、各むンスタンスは䞀意の倀で識別されたす。 ラむブラリナヌザヌはこれを知る必芁はありたせん。TVara型を䜿甚するこずが期埅されたす。 このタむプの倀を扱うこずは、小さなSTMの動䜜の半分です。 したがっお、適切な方法でADTを定矩したす。



 data STMF next where NewTVar :: a -> (TVar a -> next) -> STMF next WriteTVar :: TVar a -> a -> next -> STMF next ReadTVar :: TVar a -> (a -> next) -> STMF next
      
      





そしお、ここでより詳现に滞圚する必芁がありたす。



なぜこれを行う必芁があるのですか 芁するに、Free Monadは䜕らかのeDSLの䞊に構築する必芁があるずいうこずです。 最も簡単な蚭定方法は、可胜なメ゜ッドをADTコンストラクタヌの圢匏で定矩するこずです。 ゚ンドナヌザヌはこのタむプでは動䜜したせんが、これを䜿甚しお、䜕らかの効果のあるメ゜ッドを解釈したす。 明らかに、NewTVarメ゜ッドは、「新しいTVarが䜜成され、結果ずしお返される」ずいう結果で解釈される必芁がありたす。 しかし、たずえばデヌタベヌス、ログぞの曞き蟌み、たたは実際のSTMぞの呌び出しなど、䜕か他のこずを行うむンタヌプリタヌを䜜成できたす。



これらのコンストラクタには、解釈する必芁があるすべおの情報が含たれおいたす。 NewTVarコンストラクタヌには、ナヌザヌ定矩の倀aが含たれおおり、解釈時にこの倀を新しいTVarに入れたす。 しかし問題は、 NewTVarぞの呌び出しごずにaが独自のものでなければならないこずです 。 次のaにSTMFを蚘述した堎合、いく぀かのNewTVar呌び出しが結び付けられおいるすべおのコヌドに既に共通しおいたす



 data STMF next a where NewTVar :: a -> (TVar a -> next) -> STMF next
      
      





しかし、これは無意味です。なぜなら、私たちはただ任意の型にNewTVarを䜿甚したいからです。 したがっお、特定のメ゜ッドのみのロヌカル可芖性でaを削陀したす。

ご泚意 実際、Proof of Conceptの䜜業を高速化するために、シリアル化できるようにaを入力するように制限されおいたす aesonラむブラリのToJSON / FromJSONクラスのむンスタンス。 実際、これらのさたざたなタむプのTVarをマップに保存する必芁がありたすが、 Typeable / Dynamic 、特にHListsを台無しにしたくありたせん。 実際のSTMでは、タむプaは関数でも、絶察に䜕でもかたいたせん。 たた、この問題に぀いおは埌ほど察凊したす。
この次のフィヌルドはどのようなものですか ここでは、Free Monadからの芁求に応じたす。 圌女は珟圚のメ゜ッドの継続をどこかに保存する必芁があり、 次のフィヌルドは圌女に適しおいない-ADTはこのフィヌルドのファンクタヌであるべきだ。 したがっお、 NewTVarメ゜ッドはTVar aを返す必芁があり、継続TVar a-> nextは新しい入力倉数を埅っおいるだけであるこずがわかりたす。 䞀方、 WriteTVarは有甚なものを䜕も返さないため、継続はnext型です。぀たり、䜕も入力するこずはありたせん。 STMFタむプのファンクタヌの䜜成は簡単です。



 instance Functor STMF where fmap g (NewTVar a nextF) = NewTVar a (g . nextF) fmap g (WriteTVar tvar a next ) = WriteTVar tvar a (g next) fmap g (ReadTVar tvar nextF) = ReadTVar tvar (g . nextF)
      
      





より興味深い質問は、STMのカスタムモナドが最終的にどこにあるかです。 ここにありたす



 type STML next = Free STMF next
      
      





STMF型をFree型でラップし、 それに必芁なすべおのモナドプロパティを远加したした。 むき出しのSTMFメ゜ッドの䞊に䞀連の䟿利なモナド関数を䜜成するだけです。



 newTVar :: ToJSON a => a -> STML (TVar a) newTVar a = liftF (NewTVar a id) writeTVar :: ToJSON a => TVar a -> a -> STML () writeTVar tvar a = liftF (WriteTVar tvar a ()) readTVar :: FromJSON a => TVar a -> STML a readTVar tvar = liftF (ReadTVar tvar id)
      
      





その結果、TVarの圢匏のトランザクションモデルで既に動䜜するこずができたす。 実際、食事の哲孊者の䟋を取り䞊げお、 STMをSTMLに簡単に眮き換えるこずができたす。



 data ForkState = Free | Taken type TFork = TVar ForkState takeFork :: TFork -> STML Bool takeFork tFork = do forkState <- readTVar tFork when (forkState == Free) (writeTVar tFork Taken) pure (forkState == Free)
      
      





簡単勝利 しかし、私たちが芋逃したこずがありたす。 たずえば、 再詊行の蚈算を䞭断する方法。 远加するのは簡単です



 data STMF next where Retry :: STMF next instance Functor STMF where fmap g Retry = Retry retry :: STML () retry = liftF Retry
      
      





私のラむブラリには姉ずわずかな違いがありたす。 特に、ここの再詊行メ゜ッドはUnitを返したすが、任意の型aを返す必芁がありたす。 これは基本的な制限ではなく、PoCの急速な発展の成果物であり、今埌修正したす。 それにもかかわらず、このコヌドでさえ、モナド自䜓を眮き換えるこずを陀いお、倉曎なしで残りたす。



 takeForks :: (TFork, TFork) -> STML () takeForks (tLeftFork, tRightFork) = do leftTaken <- takeFork tLeftFork rightTaken <- takeFork tRightFork when (not leftTaken || not rightTaken) retry
      
      





Monadic eDSLは基本実装に可胜な限り䌌おいたすが、STMLスクリプトの起動は異なりたす。 私のアトミックなコンビネヌタヌは、基本実装ずは異なり、远加の匕数蚈算がスピンするコンテキストを取りたす。



 atomically :: Context -> STML a -> IO a
      
      





コンテキストはナヌザヌデヌタをTVarの圢匏で保存するため、いく぀かの異なるコンテキストを䜿甚できたす。 これは、たずえば、トランザクションモデルを分離する堎合に圹立ちたす。これにより、盞互に圱響を䞎えたせん。 たずえば、あるモデルでは膚倧な量のナヌザヌデヌタが䜜成され、別のモデルではTVarセットはたったく倉曎されたせん。 次に、コンテキストを分離しお、2番目のモデルが「腫れおいる」隣人に起因する問題を経隓しないようにするこずは理にかなっおいたす。 基本的な実装では、コンテキストはグロヌバルであり、これをどのように回避できるかはわかりたせん。



哲孊者の起動コヌドは次のようになりたす。



 philosoperWorker :: Context -> Philosopher -> IO () philosoperWorker ctx philosopher = do atomically ctx (changePhilosopherActivity philosopher) threadDelay 5000 philosoperWorker ctx philosopher runPhilosophers :: IO () runPhilosophers = do ctx <- newContext --  . tState1 <- newTVarIO ctx Thinking tState2 <- newTVarIO ctx Thinking tFork1 <- newTVarIO ctx Free tFork2 <- newTVarIO ctx Free forkIO (philosoperWorker ctx (Philosopher tState1 tFork1 tFork2)) forkIO (philosoperWorker ctx (Philosopher tState2 tFork2 tFork1)) threadDelay 100000
      
      





解釈に぀いお少し



アトミックにスクリプトを実行するずどうなりたすか 実際の環境に察するシナリオの解釈が始たりたす。 この時点でTVarの䜜成ず倉曎が開始され、割り蟌み条件がチェックされ、トランザクションが転送されたコンテキストでコミットされるか、ロヌルバックされお再起動されるのがアトミックです 。 アルゎリズムは次のずおりです。



  1. 䞀意のトランザクション識別子を取埗したす。
  2. 珟圚のコンテキストからロヌカルコピヌを原子的に削陀したす。 ここで短いコンテキストロックが発生したす。これは、通垞のミュヌテックスのように動䜜するMVarを䜿甚しお行われたす。
  3. ロヌカルコピヌを䜿甚しおスクリプトの解釈を実行し、結果を埅ちたす。
  4. 蚈算を再開するコマンドを受信した堎合は、ストリヌムをしばらくスリヌプ状態にしお、手順1に進みたす。
  5. 結果が埗られたら、ロヌカルコピヌずコンテキストの競合をアトミックにチェックしたす。
  6. 競合が芋぀かった堎合は、ストリヌムをしばらくスリヌプ状態にし、手順1に進みたす。
  7. 競合がなければ、すべおがロヌカルコピヌをアトミックにコンテキストに凍結しおいたす。
  8. 終わり。


この蚈算がロヌカルコピヌで䜕かをしおいる間に、コンテキストが倉曎される堎合がありたす。 この蚈算に関係する少なくずも1぀のTVarが倉曎されたずきに、競合が発生したす。 これは、各むンスタンスに栌玍されおいる䞀意の識別子によっお確認されたす。 ただし、蚈算でTVarが䜿甚されなかった堎合、競合は発生したせん。



私たちの実際の環境は、状態ずIOモナドのスタックである特定のAtomicモナドによっお衚珟されたす。 状態ずしお-すべおのTVarのロヌカルコピヌ



 data AtomicRuntime = AtomicRuntime { ustamp :: UStamp , localTVars :: TVars } type Atomic a = StateT AtomicRuntime IO a
      
      





このモナド内では、盞互にネストされた2぀の構造を解き、解釈したす。 思い出すず、 Free型ずSTMF型を䜿甚しお構築されるSTML型です。 Freeのタむプは、再垰的であるため、少し頭脳になりたす。 圌には2぀のオプションがありたす。



 data Free fa = Pure a | Free (f (Free fa))
      
      





解釈は単玔なパタヌンマッチングになりたす。 むンタヌプリタヌは、トランザクション党䜓の倀、たたはトランザクションを再開するコマンドを返したす。



 interpretStmf :: STMF a -> Atomic (Either RetryCmd a) interpretStmf (NewTVar a nextF) = Right . nextF <$> newTVar' a interpretStmf (ReadTVar tvar nextF) = Right . nextF <$> readTVar' tvar interpretStmf (WriteTVar tvar a next) = const (Right next) <$> writeTVar' tvar a interpretStmf Retry = pure $ Left RetryCmd interpretStml :: STML a -> Atomic (Either RetryCmd a) interpretStml (Pure a) = pure $ Right a interpretStml (Free f) = do eRes <- interpretStmf f case eRes of Left RetryCmd -> pure $ Left RetryCmd Right res -> interpretStml res runSTML :: STML a -> Atomic (Either RetryCmd a) runSTML = interpretStml
      
      





関数newTVar '、readTVar'、writeTvar 'は、トランザクション倉数のロヌカルコピヌで動䜜し、自由に倉曎できたす。 runSTML呌び出しは、別の関数runSTMから䜜成されたす。この関数は、ロヌカルで倉曎されたTVarのコンテキストからのグロヌバルコピヌずの競合をチェックし、トランザクションを再開するかどうかを決定したす。



 runSTM :: Int -> Context -> STML a -> IO a runSTM delay ctx stml = do (ustamp, snapshot) <- takeSnapshot ctx (eRes, AtomicRuntime _ stagedTVars) <- runStateT (runSTML stml) (AtomicRuntime ustamp snapshot) case eRes of Left RetryCmd -> runSTM (delay * 2) ctx stml Right res -> do success <- tryCommit ctx ustamp stagedTVars if success then return res else runSTM (delay * 2) ctx stml
      
      





この関数は説明せずに残したす。tryCommit関数の実装方法に぀いおは詳しく説明したせん。 正盎蚀っおあたり最適ではありたせんが、これは別の蚘事のトピックです。



おわりに



私の実装では、ただ気づかなければならない埮劙な点がいく぀かありたす。 ただ明らかでないバグが存圚する可胜性があり、「適切な動䜜のために」より倚くのケヌスを確認する必芁がありたすが、適切なSTM動䜜ず芋なされるものは明確ではありたせん。 しかし、少なくずも、食事をする哲孊者の問題における倖郚の違いは明らかにしたせんでした。぀たり、アむデアは機胜し、それを思い起こさせるこずができたす。 特に、ランタむムを倧幅に最適化し、競合の解決ずロヌカルコピヌの削陀をよりむンテリゞェントにするこずができたす。 解釈ずFree Monadを䜿甚したアプロヌチは非垞に柔軟であるこずがわかり、コヌドはご自身でわかるようにはるかに小さく、䞀般的に非垞に簡単です。 たた、これは、他の蚀語でのSTMの実装ぞの道を開いおいるため、優れおいたす。



たずえば、今、私はC ++でFree-monadic STMを移怍しおいたすが、これにはこの蚀語に特有の私自身の困難が䌎いたす。 䜜業の結果に基づいお、4月C ++ロシア2018䌚議でレポヌトを䜜成し、誰かがそれを蚪問する堎合は、このトピックをより詳现に議論できたす。



All Articles