Экземпляр MonadTransControl для FreeT

Можно ли реализовать MonadTransControl для FreeT? Я начал со следующего, но застрял:

instance (Functor f) => MonadTransControl (FreeT f) where
  newtype StT (FreeT f) r = FreeTStT r
  liftWith unlift = lift $ unlift $ error "Stuck here"
  restoreT inner = do
    FreeTStT r <- lift inner
    return r

Если он нереализуем, то почему и возможно ли каким-то образом расширить конкретную свободную реализацию функтора, чтобы сделать ее реализуемой?


person Nikita Volkov    schedule 19.05.2014    source источник
comment
Глядя на экземпляры MonadTransControl для других типов, кажется, что StT (FreeT f) r должно быть чем-то вроде FreeTStT (Free f r). В противном случае вы не фиксируете какое-либо монадическое состояние FreeT f и не используете f.   -  person fizruk    schedule 19.05.2014
comment
И с этой реализацией StT должно быть ясно, как определить liftWith и restoreT.   -  person fizruk    schedule 19.05.2014
comment
Вы можете прочитать этот другой вопрос и ответ, которые кажутся связанными.   -  person Gabriel Gonzalez    schedule 20.05.2014
comment
@GabrielGonzalez Другими словами, это невозможно?   -  person Nikita Volkov    schedule 20.05.2014


Ответы (1)


Отказ от ответственности: оказывается, вам нужно Traversable f ограничение для MonadTransControl экземпляра.

Внимание: экземпляр в этом ответе не подчиняется всем законам MonadTransControl

Прагмы и импорт

{-# LANGUAGE TypeFamilies #-}

import qualified Data.Traversable as T
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Control
import Control.Monad.Trans.Free
import qualified Control.Monad.Free as F

Свободное монадическое состояние

Как я сказал в комментариях, правильное «монадное состояние» FreeT f должно быть Free f (тот, что из Control.Monad.Free):

instance T.Traversable f => MonadTransControl (FreeT f) where
  newtype StT (FreeT f) a = StTFreeT { getStTFreeT :: F.Free f a }

Теперь реализация restoreT немного меняется:

  restoreT inner = do
    StTFreeT m <- lift inner
    F.toFreeT m

liftWith реализация

Прежде чем мы рассмотрим реализацию, давайте посмотрим, каким должен быть тип liftWith:

liftWith :: Monad m => (Run (FreeT f) -> m a) -> FreeT f m a

И Run (FreeT f) на самом деле

forall n b. Monad n => FreeT f n b -> n (StTFreeT f b)

Итак, реализация будет такой:

liftWith unlift = lift $ unlift (liftM StTFreeT . pushFreeT)

Остальное просто:

pushFreeT :: (T.Traversable f, Monad m) => FreeT f m a -> m (F.Free f a)
pushFreeT m = do
  f <- runFreeT m
  case f of
    Pure x -> return (return x)
    Free y -> liftM wrap $ T.mapM pushFreeT y

Почему Traversable?

Как видите, проблема связана с функцией pushFreeT: она использует T.mapM (то есть traverse, но с ограничением Monad). Зачем он нам там нужен? Если вы посмотрите на определение FreeT, то заметите, что (NB: это грубо, я забыл про Pure здесь):

FreeT f m a ~ m (f (m (f ... )))

И в результате pushFreeT нам нужно m (Free f a):

m (Free f a) ~ m (f (f (f ... )))

Итак, нам нужно «протолкнуть» все f в конец и соединить все m в голове. Таким образом, нам нужна операция, которая позволяет нам протолкнуть один f через одиночный m, и это именно то, что дает нам T.mapM pushFreeT:

mapM :: (Monad m, Traversable t) => (a -> m b) -> t a -> m (t b)
mapM pushFreeT :: Traversable t => t (FreeT t m a) -> m (t (Free t a))

Законы

Каждый экземпляр класса обычно поставляется с законами. MonadTransControl не является исключением, поэтому давайте проверим, выполняются ли они для этого экземпляра:

liftWith . const . return = return
liftWith (const (m >>= f)) = liftWith (const m) >>= liftWith . const . f

Эти два закона, очевидно, следуют из законов для MonadTrans и определения liftWith.

liftWith (\run -> run t) >>= restoreT . return = t

Судя по всему, этот закон не выполняется. Это связано с тем, что слои монад в t свернуты, когда мы pushFreeT. Таким образом, реализованный liftWith объединяет эффекты во всех слоях FreeT f m, оставляя нам эквивалент m (Free f).

person fizruk    schedule 19.05.2014
comment
Спасибо. Хотя все это имеет смысл, кажется невозможным реализовать экземпляр Traversable. Например, как бы вы поступили с таким тривиальным функтором, как data IOF z = PutStrLn String z | GetArg (String -> z)? В то время как операция PutStrLn не создает проблем, операция, требующая оценки, такая как GetArg, делает невозможным выполнение операции перемещения. - person Nikita Volkov; 20.05.2014
comment
@NikitaVolkov AFAICT можно TH-генерировать экземпляры для функторов, где аргумент имеет только строго положительные вхождения (например, String -> a или a или (Int, Either a String), но не (a -> r) -> r)). Хотели бы вы увидеть такое решение? - person fizruk; 21.05.2014
comment
@NikitaVolkov Я обновил свой ответ, чтобы отразить тот факт, что этот экземпляр не может подчиняться одному из MonadTransControl законов. Итак, как предполагает этот другой ответ, не может быть правильного экземпляра для FreeT. - person fizruk; 21.05.2014