私は大きな結び目を結ぶことを含むHaskellプロジェクトに取り組んでいます:グラフのシリアル化された表現を解析しています。各ノードはファイルにオフセットされており、そのオフセットによって別のノードを参照する場合があります。したがって、解析中にオフセットからノードへのマップを作成する必要があります。これは、do rec
ブロック内で自分自身にフィードバックできます。
私はこれを機能させており、ちょっと-ちょっと-合理的に抽象化されて-風のStateT
モナド変換子になっています:
{-# LANGUAGE DoRec, GeneralizedNewtypeDeriving #-}
import qualified Control.Monad.State as S
data Knot s = Knot { past :: s, future :: s }
newtype RecStateT s m a = RecStateT (S.StateT (Knot s) m a) deriving
( Alternative
, Applicative
, Functor
, Monad
, MonadCont
, MonadError e
, MonadFix
, MonadIO
, MonadPlus
, MonadReader r
, MonadTrans
, MonadWriter w )
runRecStateT :: RecStateT s m a -> Knot s -> m (a, Knot s)
runRecStateT (RecStateT st) = S.runStateT st
tie :: MonadFix m => RecStateT s m a -> s -> m (a, s)
tie m s = do
rec (a, Knot s' _) <- runRecStateT m (Knot s s')
return (a, s')
get :: Monad m => RecStateT s m (Knot s)
get = RecStateT S.get
put :: Monad m => s -> RecStateT s m ()
put s = RecStateT $ S.modify $ \ ~(Knot _ s') -> Knot s s'
このtie
関数は魔法が起こる場所です。呼び出しによってrunRecStateT
値と状態が生成され、それを独自の未来としてフィードします。get
過去と未来の両方の状態から読み取ることがput
できますが、「現在」を変更することしかできないことに注意してください。
質問1:これは一般的にこの結び目を結ぶパターンを実装するための適切な方法のように見えますか?またはさらに良いことに、誰かがこれに対する一般的な解決策を実装しましたが、ハッキングを覗き見したときに見落としていましたか?モナドはおそらくもっとエレガントに見えたので(Dan Burtonからの同様の投稿Cont
を参照)、しばらくの間モナドに頭を打ちましたが、うまくいきませんでした。
完全に主観的な質問2:私の呼び出しコードが最終的にどのように見えるかについて私は完全にわくわくしていません:
do
Knot past future <- get
let {- ... -} = past
{- ... -} = future
node = {- ... -}
put $ {- ... -}
return node
ここでの実装の詳細は省略されています。明らかに重要な点は、past
and状態を取得し、letバインディング内future
でそれらをパターンマッチングして(または前のパターンを明示的に遅延させて)、気になるものを抽出してから、ノードを構築して更新する必要があることです。私の状態と最後にノードを返します。不必要に冗長に思えますが、特に、と状態を抽出するパターンを誤って厳密にすることがいかに簡単であるかが嫌いです。それで、誰かがより良いインターフェースを考えることができますか?past
future