nus*_*hio 21 haskell operational monad-transformers free-monad
可以为FreeT/ProgramT创建的monad变换器提供类似mtl的机制吗?
我对历史的理解如下.曾几何时,monad变压器被发明了.然后人们开始在另一个上堆叠monad变换器,然后发现插入lift
到处都很烦人.然后有几个人发明了monad类,所以我们可以ask :: m r
在任何monad中m
这样做MonadReader r m
.这可以通过让每个monad类穿透每个monad变换器来实现
(Monoid w, MonadState s m) => MonadState s (WriterT w m)
MonadWriter w m => MonadWriter w (StateT s m)
你需要为每对monad变换器提供这样的实例声明对,所以当有n个 monad变换器时,你需要n ^ 2个成本.然而,这不是一个大问题,因为人们将主要使用预定义的monad并且很少创建自己的monad.到目前为止,我理解这个故事,并且在下面的问答中也详细说明:
然后我的问题是新的免费monad http://hackage.haskell.org/package/free和操作monads http://hackage.haskell.org/package/operational.它们允许我们编写自己的DSL并将其用作monad,只需将语言定义为某种代数data
类型(Operational甚至不需要Functor
实例).好消息是我们可以免费获得monad和monad变换器; 那么monad课怎么样?坏消息是"我们很少定义我们自己的monad变换器"的假设不再成立.
为了解这个问题,我做了两个ProgramT
,让它们相互渗透;
https://github.com/nushio3/practice/blob/master/operational/exe-src/test-05.hs
该operational
包不支持monad类,所以我采用了另一个实现minioperational
并将其修改为我需要的工作; https://github.com/nushio3/minioperational
不过,我需要专门的实例声明
instance (Monad m, Operational ILang m) => Operational ILang (ProgramT SLang m) where
因为以下形式的一般声明会导致不可判定的实例.
instance (Monad m, Operational f m) => Operational f (ProgramT g m) where
我的问题是,我们怎样才能让我们的运营单子更容易相互渗透.或者,我是否希望能够对任何可操作的Monad进行渗透.
我也想知道正确的渗透技术术语:)
我尝试了一些不同的方法,至少给出了部分答案.由于堆叠monad有时会出现问题,并且我们知道所有monad都是从某种数据类型构造的,所以我尝试组合数据类型.
我感觉更舒服MonadFree
所以我使用它,但我想也可以使用类似的方法Operational
.
让我们从数据类型的定义开始:
{-# LANGUAGE DeriveFunctor, FlexibleContexts,
FlexibleInstances, FunctionalDependencies #-}
import Control.Monad
import Control.Monad.Free
data SLang x = ReadStr (String -> x) | WriteStr String x
deriving Functor
data ILang x = ReadInt (Int -> x) | WriteInt Int x
deriving Functor
Run Code Online (Sandbox Code Playgroud)
为了将两个仿函数组合在一起以在免费monad中使用它们,让我们定义它们的副产品:
data EitherF f g a = LeftF (f a) | RightF (g a)
deriving Functor
Run Code Online (Sandbox Code Playgroud)
如果我们创建一个免费的monad EitherF f g
,我们可以调用它们的命令.为了使这个过程透明,我们可以使用MPTC允许从每个仿函数转换到目标仿函数:
class Lift f g where
lift :: f a -> g a
instance Lift f f where
lift = id
instance Lift f (EitherF f g) where
lift = LeftF
instance Lift g (EitherF f g) where
lift = RightF
Run Code Online (Sandbox Code Playgroud)
现在我们可以调用lift
并将任一部分转换为副产品.
具有辅助功能
wrapLift :: (Functor g, Lift g f, MonadFree f m) => g a -> m a
wrapLift = wrap . lift . fmap return
Run Code Online (Sandbox Code Playgroud)
我们最终可以创建通用函数,允许我们从任何可以升级为仿函数的命令中调用命令:
readStr :: (Lift SLang f, MonadFree f m) => m String
readStr = wrapLift $ ReadStr id
writeStr :: (Lift SLang f, MonadFree f m) => String -> m ()
writeStr x = wrapLift $ WriteStr x ()
readInt :: (Lift ILang f, MonadFree f m) => m Int
readInt = wrapLift $ ReadInt id
writeInt :: (Lift ILang f, MonadFree f m) => Int -> m ()
writeInt x = wrapLift $ WriteInt x ()
Run Code Online (Sandbox Code Playgroud)
然后,程序可以表示为
myProgram :: (Lift ILang f, Lift SLang f, MonadFree f m) => m ()
myProgram = do
str <- readStr
writeStr "Length of that str is"
writeInt $ length str
n <- readInt
writeStr "you wanna have it n times; here we go:"
writeStr $ replicate n 'H'
Run Code Online (Sandbox Code Playgroud)
没有定义任何进一步的实例
虽然以上所有都很好用,但问题是如何一般地运行这样的组合免费monad.我不知道是否有可能拥有一个完全通用的,可组合的解决方案.
如果我们只有一个基本仿函数,我们就可以运行它
runSLang :: Free SLang x -> String -> (String, x)
runSLang = f
where
f (Pure x) s = (s, x)
f (Free (ReadStr g)) s = f (g s) s
f (Free (WriteStr s' x)) _ = f x s'
Run Code Online (Sandbox Code Playgroud)
如果我们有两个,我们需要线程化它们的状态:
runBoth :: Free (EitherF SLang ILang) a -> String -> Int -> ((String, Int), a)
runBoth = f
where
f (Pure x) s i = ((s, i), x)
f (Free (LeftF (ReadStr g))) s i = f (g s) s i
f (Free (LeftF (WriteStr s' x))) _ i = f x s' i
f (Free (RightF (ReadInt g))) s i = f (g i) s i
f (Free (RightF (WriteInt i' x))) s _ = f x s i'
Run Code Online (Sandbox Code Playgroud)
我想一种可能是表达运行使用仿函数iter :: Functor f => (f a -> a) -> Free f a -> a
的自由,然后创建一个类似的,结合功能
iter2 :: (Functor f, Functor g)
=> (f a -> a) -> (g a -> a) -> Free (EitherF f g) a -> a
Run Code Online (Sandbox Code Playgroud)
但我没有时间尝试一下.
归档时间: |
|
查看次数: |
575 次 |
最近记录: |