Haskell 如何缩放monad转换器?
再次感谢你的帮助 我正在广泛使用E.Kmett的镜头库,为了避免X/Y问题,我将解释一些上下文 我正在开发一个可扩展文本编辑器,希望为扩展编写器提供一个monad DSL,Haskell 如何缩放monad转换器?,haskell,haskell-lens,state-monad,Haskell,Haskell Lens,State Monad,再次感谢你的帮助 我正在广泛使用E.Kmett的镜头库,为了避免X/Y问题,我将解释一些上下文 我正在开发一个可扩展文本编辑器,希望为扩展编写器提供一个monad DSL,转换是一个monad转换器堆栈,在存储类型上有一个StateT,它基本上存储整个文本编辑器。在存储中有一个编辑器,它具有缓冲区s。用户可以指定一个变更对整个商店进行操作,但为了简化操作,我还提供了一个变更,它只在一个缓冲区上操作 我计划通过使用名为bufDo的助手来实现这一点,该助手在每个Buffer上运行buparty,而f
转换
是一个monad转换器堆栈,在存储
类型上有一个StateT,它基本上存储整个文本编辑器。在存储
中有一个编辑器
,它具有缓冲区
s。用户可以指定一个变更
对整个商店进行操作,但为了简化操作,我还提供了一个变更
,它只在一个缓冲区上操作
我计划通过使用名为bufDo
的助手来实现这一点,该助手在每个Buffer
上运行buparty
,而focusDo
在“聚焦的Buffer
上运行buparty
。以下是一些上下文:
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)
下面是我建议的bufDo
和focusDo
的实现:
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指定一个实例,但我不太确定如何做到这一点
有人有想法吗?如果你能解释为什么我需要一个缩放实例(如果是这样的话),我也会很高兴
干杯 似乎有一个
缩放的
类型族,用于指定缩放时的“效果”。例如,在某些情况下,monad transformer的Zoomed
类型实例似乎与底层monad的Zoomed
类型实例相匹配
type Zoomed (ReaderT * e m) = Zoomed m
鉴于变更
和变更
只是状态转换器上的新类型,或许我们也可以这样做:
{-# language TypeFamilies #-}
{-# language UndecidableInstances #-}
{-# language MultiParamTypeClasses #-}
type instance Zoomed BufAction = Zoomed (StateT Buffer IO)
然后我们必须提供Zoom
实例Zoom
是一个多参数类型类,四个参数似乎是原始单子、缩小单子、原始状态、缩小状态:
我们只需展开buaction
,缩放底层monad,并将其包装为alternation
此基本测试类型检查:
foo :: Alteration ()
foo = zoom (editor.buffers.traversed) (return () :: BufAction ())
我相信您可以避免定义
Zoom
实例,并使用一个专用的zoombulation
函数
zoomBufActionToAlteration :: LensLike' (Zoomed (StateT Buffer IO) a) Store Buffer
-> BufAction a
-> Alteration a
zoomBufActionToAlteration f (BufAction a) = Alteration (zoom f a)
但是,如果你有很多不同的可缩放功能,记住每个缩放功能的名称可能是件麻烦事。这就是typeclass可以提供帮助的地方。作为答案@danidiaz的补充
基本上,您可以通过以下方式避免
Zoom
实例:
bufDo :: BufAction () -> Alteration ()
bufDo = Alteration . zoom (editor . buffers . traverse) . runBufAction
这本质上是一个复制品。我看到了那个,但还是有点困惑,他们给出了一个解决方案,但没有解释清楚。我会试试这个!谢谢您能否更深入地解释一下
类型实例
行?我还没有学习类型族:还有,为什么我们需要不可判定的实例?最后,为什么我既需要类型族缩放
又需要缩放实例?我很想了解为什么这一切都有效,为什么这一切都是必需的。干杯!:)@Chris Penner类型族在类型级别上有点像函数。它们接受一种类型(在我们的例子中是buaction
),并生成另一种类型(在我们的例子中是Zoomed(StateT Buffer IO)
)。与常规函数相比,它们有一些特殊性:语法不同,不同的“案例”可以分散在不同的模块中,等等。您可以使用:kind在ghci
中“运行”类型族代码>命令,比如:种类!缩放(StateT Buffer IO)
@Chris Penner不可判定实例
是必需的,因为在处理复杂实例定义时,GHC可能无法确定类型检查是否可以终止。打开UndedicatableInstances
会说“编译器,不要担心没有终止的类型检查,如果需要太长时间,我只需按Ctrl-C键就可以了。”当然,如果类型检查真的终止,这不是一个有害的扩展。这非常有效!我很感谢@danidiaz的解释,当然,很高兴终于了解了类型族!但是有什么理由我应该使用类型族向导而不是此解决方案吗?是的,如果您想添加使用zoom
或类似zoombulationtoalteration
之类的功能,那么我应该使用类型族向导。如果你有很多不同的可缩放的东西,这个原因可能(如@danidiaz所写)。
bufDo :: BufAction () -> Alteration ()
bufDo = Alteration . zoom (editor . buffers . traverse) . runBufAction