Отвращение в Haskell: от Tardis до RevState

В следующей программе используется состояние обратного перемещения, предоставляемое монадой Tardis.

{-# LANGUAGE RecursiveDo #-}

import Control.Monad.Tardis

lastOccurrence :: Int -> Tardis [Int] () Bool
lastOccurrence x = mdo
  sendPast (x : xs)
  xs <- getFuture
  return (not (elem x xs))

lastOccurrences :: [Int] -> Tardis [Int] () [Bool]
lastOccurrences xs = mapM lastOccurrence xs

main :: IO ()
main =
  print $ flip evalTardis ([], ()) $ lastOccurrences [3,4,6,7,4,3,5,7]

Как я могу заменить монаду Тардис монадой в обратном государстве? Следуя моему предложению, main петли навсегда вместо печати [False,False,True,False,True,True,True,True], как в случае с вышеуказанной программой.

{-# LANGUAGE RecursiveDo #-}

import Control.Monad.RevState

lastOccurrence :: Int -> State [Int] Bool
lastOccurrence x = mdo
  put (x : xs)
  xs <- get
  return (not (elem x xs))

lastOccurrences :: [Int] -> State [Int] [Bool]
lastOccurrences xs = mapM lastOccurrence xs

main :: IO ()
main =
  print $ flip evalState [] $ lastOccurrences [3,4,6,7,4,3,5,7]

Ответы

Ответ 1

Теперь я загрузил источник как Tardis, так и RevState, и я начал их взламывать, пока они не станут почти такими же:

  • Я игнорировал все, что было за пределами модулей Trans.{Tarids,RevState}, поэтому мне не нужно беспокоиться о классах
  • Я удалил вперед-распространяющееся состояние Tardis
  • Я переименовал Tardis в State

После небольшого переупорядочения кода я оказался в ситуации, когда ваш пример Tardis -using по-прежнему работает, и ваш пример RevState -using все еще не работает, и их различие минимально.

Что это за минимальная разница, спросите вы? Неудивительно, что экземпляр MonadFix. Tardis имеет это:

instance MonadFix m => MonadFix (TardisT bw fw m) where
  mfix f = TardisT $ \s -> do
    rec (x, s') <- runTardisT (f x) s
    return (x, s')

тогда как RevState имеет это:

instance MonadFix m => MonadFix (StateT s m) where
  mfix f = StateT $ \s ->
    mfix (\(x, _) -> runStateT (f x) s)

Пока они кажутся похожими, большая разница в том, что RevState один строк в конструкторе кортежа, тогда как Tardis - ленивый. (см., например, документацию GHC на RecursiveDo, чтобы увидеть, что Tardis один desugars в неопровержимое совпадение шаблона в лямбда, переданном в mfix).

Действительно, меняя реализацию RevState так, чтобы

instance MonadFix m => MonadFix (StateT s m) where
  mfix f = StateT $ \s -> do
    mfix (\ ~(x, _) -> runStateT (f x) s)

исправляет исходную программу RevState -using.