22

FreeT / ProgramT 创建的 monad 转换器是否有类似 mtl 的机制?

我对历史的理解如下。曾几何时,monad 转换器被发明了。然后人们开始一个接一个地堆叠 monad 转换器,然后发现lift到处都插入很烦人。然后有几个人发明了单子类,这样我们就可以ask :: m r在任何单子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 Transformers 避免升力

然后我的问题是新的 Free monads http://hackage.haskell.org/package/free和 Operational monads http://hackage.haskell.org/package/operational。它们允许我们编写自己的 DSL 并将其用作 monad,只需将语言定义为某种代数data类型(操作甚至不需要Functor实例)。好消息是我们可以免费获得 monad 和 monad 转换器;那么monad类呢?坏消息是“我们很少定义自己的 monad 转换器”的假设不再成立。

作为理解这个问题的尝试,我做了两个ProgramTs,让它们互相穿透;

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

我的问题是,我们怎样才能更容易让我们的操作单子相互渗透。或者,我是否希望对任何不合时宜的 Operational monad 有所了解。

我也想知道正确的渗透技术术语:)

4

1 回答 1

6

我尝试了一种不同的方法,它至少给出了部分答案。由于堆叠 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

为了将两个函子组合在一起以便在自由 monad 中使用它们,让我们定义它们的联积:

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

如果我们创建一个免费的 monad over 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

现在我们可以调用lift并将任一部分转换为副产品。

带有辅助功能

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

我们最终可以创建通用函数,允许我们从任何我们可以提升到函子中的命令中调用命令:

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

那么程序可以表示为

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'

没有定义任何进一步的实例。


虽然上述所有工作都很好,但问题是如何一般地运行这种组合的自由单子。我不知道是否有可能拥有一个完全通用的、可组合的解决方案。

如果我们只有一个基础函子,我们可以将其运行为

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'

如果我们有两个,我们需要线程化它们的状态:

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'

我想一种可能性是使用iter :: Functor f => (f a -> a) -> Free f a -> afrom free来表达运行仿函数,然后创建一个类似的组合函数

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

但是我还没有时间去尝试。

于 2013-08-02T11:22:10.193 回答