Если вы хотите немного изменить исходный интерпретатор (изменив способ обработки терминального случая)
{-# LANGUAGE DeriveFunctor #-}
import Control.Monad.Free
import Control.Monad.Morph
import Pipes
data FooF a = Foo String a | Bar Int a deriving (Functor)
printFoo :: Free FooF a -> IO a
printFoo (Free (Foo s n)) = print s >> printFoo n
printFoo (Free (Bar i n)) = print i >> printFoo n
printFoo (Pure a) = return a
... тогда есть способ добавить дополнительные действия без изменения функтора или необходимости перепрофилировать его конструкторы, сохраняя при этом возможность повторно использовать интерпретатор.
В решении используются пакеты pipes
и mmorph
.
Сначала вы должны определить своего рода «предварительный интерпретатор», который поднимает свободную монаду в Producer
из pipes
. Операторы yield ()
в производителе обозначают точки, в которые вставляется дополнительное действие.
pre :: Free FooF a -> Producer () (Free FooF) a
pre (Free (Foo s n)) = lift (Free . Foo s $ return ()) >> yield () >> pre n
pre (Free (Bar i n)) = lift (Free . Bar i $ return ()) >> yield () >> pre n
pre (Pure a) = lift . Pure $ a
(В более сложном примере операторы yield
могут содержать дополнительную информацию, например сообщения журнала.)
Затем вы пишете функцию, которая применяет интерпретатор printFoo
под Producer
, используя hoist
из mmorph
:
printFooUnder :: Producer () (Free FooF) a -> Producer () IO a
printFooUnder = hoist printFoo
Итак, у нас есть функция, которая «интерпретирует» свободную монаду в IO
, но в некоторых моментах выдает ()
значений, которые мы должны решить, как обрабатывать.
Теперь мы можем определить расширенный интерпретатор, который повторно использует старый интерпретатор:
printFooWithReuse :: Show a => Free FooF a -> IO ()
printFooWithReuse foo = do
finalv <- runEffect $ for (printFooUnder . pre $ foo)
(\_ -> lift (print "extra info"))
print finalv
После тестирования вроде работает:
printFooWithReuse $ Free (Foo "nah" (Pure 4))
-- > "nah"
-- > "extra info"
-- > 4
Если вы хотите вставить дополнительные действия вручную, вы можете отказаться от написания «предварительного интерпретатора» и работать непосредственно в монаде Producer () (Free FooF)
.
(Это решение также может быть достигнуто путем наслоения бесплатного преобразователя монад вместо Producer
. Но я думаю, что использовать Producer
немного проще.)
person
danidiaz
schedule
13.12.2013