Завязывание узла с помощью монады State

Я работаю над проектом на 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(или явно сделайте предыдущий шаблон ленивым), чтобы извлечь все, что мне нужно, затем создайте свой узел, обновите мое состояние и, наконец, вернуть узел. Выглядит излишне многословным, и мне особенно не нравится, как легко случайно сделать шаблон, который извлекает прошлоеи будущеесостояниями, строгими. Итак, кто-нибудь может придумать более приятный интерфейс?

40
задан Community 23 May 2017 в 12:03
поделиться