简体   繁体   English

Monad Stack Penetration Classes with Free / Operational Monad Transformers?

[英]Monad Stack Penetration Classes with Free/Operational Monad Transformers?

Can there be mtl-like mechanism for monad transformers created by FreeT / ProgramT ? 可以为FreeT / ProgramT创建的monad变换器提供类似mtl的机制吗?

My understanding of the history is as follows. 我对历史的理解如下。 Once upon a time monad transformer was invented. 曾几何时,monad变压器被发明了。 Then people started to stack monad transformers one on other, then found it annoying to insert lift everywhere. 然后人们开始在另一个上堆叠monad变压器,然后发现在任何地方插入lift都很烦人。 Then a couple of people invented monad classes, so that we can eg ask :: mr in any monad m such that MonadReader rm . 然后有几个人发明了monad类,所以我们可以在任何monad m ask :: mr ,以便MonadReader rm This was possible by making every monad class penetrate every monad transformer, like 这可以通过让每个monad类穿透每个monad变换器来实现

(Monoid w, MonadState sm) => MonadState s (WriterT wm)
MonadWriter wm => MonadWriter w (StateT sm)

you need such pair of instance declarations for every pair of monad transformers, so when there's n monad transformers there's n ^2 costs. 你需要为每对monad变换器提供这样的实例声明对,所以当有n个 monad变换器时,你需要n ^ 2个成本。 This was not a large problem, however, because people will mostly use predefined monads and rarely create their own. 然而,这不是一个大问题,因为人们将主要使用预定义的monad并且很少创建自己的monad。 The story so far I understand, and also is detailed eg in the following Q&A: 到目前为止,我理解这个故事,并且在下面的问答中也详细说明:

Avoiding lift with Monad Transformers 使用Monad变形金刚避免升降机

Then my problem is with the new Free monads http://hackage.haskell.org/package/free and Operational monads http://hackage.haskell.org/package/operational . 然后我的问题是新的免费monad http://hackage.haskell.org/package/free和操作monads http://hackage.haskell.org/package/operational They allow us to write our own DSL and use it as monads, just by defining the language as some algebraic data type (Operational doesn't even need Functor instances). 它们允许我们编写自己的DSL并将其用作monad,只需将语言定义为某种代数data类型(Operational甚至不需要Functor实例)。 Good news is that we can have monads and monad transformers for free; 好消息是我们可以免费获得monad和monad变换器; then how about monad classes? 那么monad课怎么样? Bad news is that the assumption "we rarely define our own monad transformers" no longer holds. 坏消息是“我们很少定义我们自己的monad变换器”的假设不再成立。

As an attempt to understand this problem, I made two ProgramT s and made them penetrate each other; 为了解这个问题,我制作了两个ProgramT并使它们相互渗透;

https://github.com/nushio3/practice/blob/master/operational/exe-src/test-05.hs https://github.com/nushio3/practice/blob/master/operational/exe-src/test-05.hs

The operational package does not support monad classes so I took another implementation minioperational and modified it to work as I need; operational包不支持monad类,所以我采用了另一个minioperational实现并将其修改为我需要的工作; https://github.com/nushio3/minioperational https://github.com/nushio3/minioperational

Still, I needed the specialized instance declaration 不过,我需要专门的实例声明

instance (Monad m, Operational ILang m) => Operational ILang (ProgramT SLang m) where

because the general declaration of the following form leads to undecidable instances. 因为以下形式的一般声明会导致不可判定的实例。

instance (Monad m, Operational fm) => Operational f (ProgramT gm) where

My question is that how can we make it easier to let our Operational monads penetrate each other. 我的问题是,我们怎样才能让我们的运营单子更容易相互渗透。 Or, is my wish to have penetration for any Operational monad ill-posed. 或者,我是否希望能够对任何可操作的Monad进行渗透。

I'd also like to know the correct technical term for penetration :) 我也想知道正确的渗透技术术语:)

I tried a bit different approach, which gives at least a partial answer. 我尝试了一些不同的方法,至少给出了部分答案。 Since stacking monads can be sometimes problematic, and we know all our monads are constructed from some data type, I tried instead to combine the data types. 由于堆叠monad有时会出现问题,并且我们知道所有monad都是从某种数据类型构造的,所以我尝试组合数据类型。

I feel more comfortable with MonadFree so I used it, but I suppose a similar approach could be used for Operational as well. 我对MonadFree感觉更舒服,所以我使用它,但我想类似的方法也可用于Operational

Let's start with the definition of our data types: 让我们从数据类型的定义开始:

{-# 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

In order to combine two functors together for using them in a free monad, let's define their coproduct: 为了将两个仿函数组合在一起以在免费monad中使用它们,让我们定义它们的副产品:

data EitherF f g a = LeftF (f a) | RightF (g a)
  deriving Functor

If we create a free monad over EitherF fg , we can call the commands from both of them. 如果我们在EitherF fg创建一个免费的monad,我们可以调用它们的命令。 In order to make this process transparent, we can use MPTC to allow conversion from each of the functor into the target one: 为了使这个过程透明,我们可以使用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

now we can just call lift and convert either part into the coproduct. 现在我们可以调用lift并将其中任何一部分转换为副产品。

With a helper function 具有辅助功能

wrapLift :: (Functor g, Lift g f, MonadFree f m) => g a -> m a
wrapLift = wrap . lift . fmap return

we can finally create generic functions that allow us to call commands from anything we can lift into a functor: 我们最终可以创建通用函数,允许我们从任何可以提升到仿函数的命令中调用命令:

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 ()

Then, the program can be expressed as 然后,程序可以表示为

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'

without defining any further instances. 没有定义任何进一步的实例


While all the above works nicely, the problem is how to generically run such composed free monads. 虽然以上所有都很好用,但问题是如何一般地运行这样的组合免费monad。 I don't know if it is even possible, to have a fully generic, composable solution. 我不知道是否有可能拥有一个完全通用的,可组合的解决方案。

If we have just one base functor, we can run it as 如果我们只有一个基本仿函数,我们就可以运行它

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'

If we have two, we need to thread the state of both of them: 如果我们有两个,我们需要线程化它们的状态:

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'

I guess one possibility would be to express running the functors using iter :: Functor f => (fa -> a) -> Free fa -> a from free and then create a similar, combining function 我想一种可能性就是使用iter :: Functor f => (fa -> a) -> Free fa -> a来表达运行仿函数iter :: Functor f => (fa -> a) -> Free fa -> a from free然后创建一个类似的组合函数

iter2 :: (Functor f, Functor g)
      => (f a -> a) -> (g a -> a) -> Free (EitherF f g) a -> a

But I haven't had time to try it out. 但我没有时间尝试一下。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM