如何缩放 monad 变压器?

How to zoom a monad transformer?

再次感谢您的帮助!

我正在广泛使用 E. Kmett 的 Lens 库,为避免 X/Y 问题,我将解释一些上下文。

我正在开发一个可扩展的文本编辑器,想为扩展作者提供一个 monad DSL,Alteration 是一个 monad 转换器堆栈,在 Store 类型上有一个 StateT,基本上存储整个文本编辑器。在 Store 内部是一个 Editor,其中包含 Buffer。用户可以指定一个 Alteration 来对整个存储进行操作,但为了简化操作,我还提供了一个 BufAction,它只对一个缓冲区进行操作。

我计划通过使用一个名为 bufDo 的助手来实现这一点,它在每个 Buffer 上运行 BufAction,而 focusDo 运行 [=20] =] 在 'focused' Buffer 上。这是一些背景信息:

data Store = Store
  { _event :: [Event]
  , _editor :: E.Editor
  , _extState :: Map TypeRep Ext
  } deriving (Show)

data Editor = Editor {
    _buffers :: [Buffer]
  , _focused :: Int
  , _exiting :: Bool
} deriving Show

data Buffer = Buffer
  { _text :: T.Text
  , _bufExts :: Map TypeRep Ext
  , _attrs :: [IAttr]
  }

newtype Alteration a = Alteration
  { runAlt :: StateT Store IO a
  } deriving (Functor, Applicative, Monad, MonadState Store, MonadIO)

newtype BufAction a = BufAction 
  { runBufAction::StateT Buffer IO a
  } deriving (Functor, Applicative, Monad, MonadState Buffer, MonadIO)

这是我为 bufDofocusDo 提出的实施方案:

bufDo :: ???
bufDo = zoom (buffers.traverse)

-- focusedBuf is a Lens' over the focused buffer (I just 'force' the traversal using ^?! in case you're wondering)
focusDo :: ???
focusDo = zoom focusedBuf

这在我看来很有意义并且接近于类型检查,但是当我尝试为它们添加类型时我有点困惑,ghc 提出了一些建议,我最终得到了这个,这是很远的来自优雅:

bufDo :: (Applicative (Zoomed BufAction ()), Zoom BufAction Alteration Buffer Store) => BufAction () -> Alteration ()

focusDo :: (Functor (Zoomed BufAction ()), Zoom BufAction Alteration Buffer Store) => BufAction () -> Alteration ()

这让 ghc 对这些定义很满意,但是当我尝试实际使用它们中的任何一个时,我得到了这些错误:

    - No instance for (Functor (Zoomed BufAction ()))
    arising from a use of ‘focusDo’

    - No instance for (Applicative (Zoomed BufAction ()))
    arising from a use of ‘bufDo’

环顾四周,我似乎需要为 Zoom 指定一个实例,但我不太确定该怎么做。

有人有想法吗?如果您能解释为什么我需要 Zoom 实例(如果是这样的话),我也很乐意。

干杯!

好像有个Zoomed类型族,用来指定我们缩放时会有什么样的"effect"。在某些情况下,monad 转换器的 Zoomed 类型实例似乎搭载在底层 monad 的 Zoomed 上,例如

type Zoomed (ReaderT * e m) = Zoomed m

鉴于 AlterationBufAction 只是状态转换器的新类型,也许我们可以做同样的事情:

{-# language TypeFamilies #-}
{-# language UndecidableInstances #-}
{-# language MultiParamTypeClasses #-}    

type instance Zoomed BufAction = Zoomed (StateT Buffer IO)

然后我们必须提供 Zoom 实例。 Zoom 是多参数类型类,四个参数好像是 original monad, zoomed out monad, 原始状态缩小状态

instance Zoom BufAction Alteration Buffer Store where
    zoom f (BufAction a) = Alteration (zoom f a)

我们只是解包 BufAction,使用底层 monad 缩放,然后包装为 Alteration

此基本测试类型检查:

foo :: Alteration ()
foo = zoom (editor.buffers.traversed) (return () :: BufAction ())

我相信你可以避免定义 Zoom 实例并有一个特殊用途的 zoomBufActionToAlteration 函数

zoomBufActionToAlteration :: LensLike' (Zoomed (StateT Buffer IO) a) Store Buffer 
                          -> BufAction a 
                          -> Alteration a
zoomBufActionToAlteration f (BufAction a) = Alteration (zoom f a)       

但是如果你有很多不同的可缩放的东西,那么记住每个缩放功能的名称可能会很麻烦。这就是类型类可以提供帮助的地方。

作为@danidiaz 回答的补充。


基本上,您可以通过这种方式避免 Zoom 实例:

bufDo :: BufAction () -> Alteration ()
bufDo = Alteration . zoom (editor . buffers . traverse) . runBufAction