Я работаю над проектом на Haskell, который включает в себя завязывание большого узла: я анализирую сериализованное представление графа, где каждый узел находится с некоторым смещением в файле и может ссылаться на другой узел по его смещению. Поэтому мне нужно построить карту от смещений до узлов во время синтаксического анализа, которую я могу передать себе в блоке do rec
.
У меня это работает, и вроде как разумно абстрагируется в StateT
-esque преобразователь монады:
{-# 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: Кажется ли это достойным способом реализации схемы завязывания узлов в целом? Или, что еще лучше, кто-то реализовал общее решение этой проблемы, которое я упустил из виду, когда копался в Hackage? Я какое-то время бился головой о монаду Cont
, так как она казалась, возможно, более элегантной (см. аналогичный постДэна Бертона), но так и не смог разобраться.
Полностью субъективный Вопрос 2: Я не совсем в восторге от того, как выглядит мой вызывающий код:
do
Knot past future <- get
let {- ... -} = past
{- ... -} = future
node = {- ... -}
put $ {- ... -}
return node
Детали реализации здесь опущены, очевидно, важным моментом является то, что я должен получить прошлое
и будущее
, сопоставьте их с шаблоном внутри привязки let(или явно сделайте предыдущий шаблон ленивым), чтобы извлечь все, что мне нужно, затем создайте свой узел, обновите мое состояние и, наконец, вернуть узел. Выглядит излишне многословным, и мне особенно не нравится, как легко случайно сделать шаблон, который извлекает прошлое
и будущее
состояниями, строгими. Итак, кто-нибудь может придумать более приятный интерфейс?