我最近一直在自学免费Free
包中的monad ,但我遇到了一个问题。我想为不同的库提供不同的免费 monad,本质上我想为不同的上下文构建 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
由于问题在于组合 monad,我的第一个想法是查看Control.Monad.Trans.Free
模块以获得快速简便的解决方案。不幸的是,有很少的例子,没有一个显示我想要做什么。此外,似乎堆叠两个或多个免费 monad 不起作用,因为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 显然很有用。