Недавно я преподавал о монаде Free
из free, но я столкнулся с проблемой. Я хотел бы иметь разные бесплатные монады для разных библиотек, по сути, я хотел бы создавать DSL для разных контекстов, но я также хотел бы объединить их вместе. В качестве примера:
{-# LANGUAGE DeriveFunctor #-}
module TestingFree where
import Control.Monad.Free
data BellsF x
= Ring x
| Chime x
deriving (Functor, Show)
type Bells = Free BellsF
data WhistlesF x
= PeaWhistle x
| SteamWhistle x
deriving (Functor, Show)
type Whistles = Free WhistlesF
ring :: Bells ()
ring = liftF $ Ring ()
chime :: Bells ()
chime = liftF $ Chime ()
peaWhistle :: Whistles ()
peaWhistle = liftF $ PeaWhistle ()
steamWhistle :: Whistles ()
steamWhistle = liftF $ SteamWhistle ()
playBells :: Bells r -> IO r
playBells (Pure r) = return r
playBells (Free (Ring x)) = putStrLn "RingRing!" >> playBells x
playBells (Free (Chime x)) = putStr "Ding-dong!" >> playBells x
playWhistles :: Whistles () -> IO ()
playWhistles (Pure _) = return ()
playWhistles (Free (PeaWhistle x)) = putStrLn "Preeeet!" >> playWhistles x
playWhistles (Free (SteamWhistle x)) = putStrLn "Choo-choo!" >> playWhistles x
Теперь я хотел бы иметь возможность создать тип BellsAndWhistles
, который позволяет мне без труда объединить функциональность как Bells
, так и Whistles
.
Поскольку проблема заключается в объединении монад, моя первая мысль заключалась в том, чтобы взглянуть на модуль Control.Monad.Trans.Free
для быстрого и простого решения. К сожалению, есть редкие примеры, и никто не показывает, что я хочу делать. Кроме того, кажется, что укладка двух или более свободных монад не работает, так как MonadFree
имеет функциональную зависимость от m -> f
. По сути, мне бы хотелось написать код вроде:
newtype BellsAndWhistles m a = BellsAndWhistles
{ unBellsAndWhistles :: ???
} deriving
( Functor
, Monad
-- Whatever else needed
)
noisy :: Monad m => BellsAndWhistles m ()
noisy = do
lift ring
lift peaWhistle
lift chime
lift steamWhistle
play :: BellsAndWhistles IO () -> IO ()
play bellsNwhistles = undefined
Но таким образом, что Bells
и Whistles
могут существовать в отдельных модулях и не должны знать о реализации друг друга. Идея заключается в том, что я могу писать автономные модули для разных задач, каждый из которых реализует собственную DSL, а затем, имея при этом способ объединить их в "большую" DSL. Есть ли простой способ сделать это?
В качестве бонуса было бы здорово использовать различные функции play*
, которые уже написаны, таким образом, что я могу их заменить. Я хочу иметь возможность использовать один бесплатный интерпретатор для отладки и другой в производстве, и, очевидно, было бы полезно иметь возможность выбирать, какая DSL отлаживается отдельно.