gpt4 book ai didi

haskell - 如何将具有类约束的多个非标准变压器组合成一个堆栈?

转载 作者:行者123 更新时间:2023-12-02 17:28:37 24 4
gpt4 key购买 nike

这将是一篇很长的文章,因为我不确定我是否以正确的心态进入这个话题,所以我将在每一步中尽可能清楚地概述我的想法。我有两个代码片段,它们是我能制作的尽可能少的,所以请随意使用它们。

我从单个变压器 FitStateT m a 开始,它只保存程序当时的状态并允许保存到磁盘:

data FitState = FitState
newtype FitStateT m a = FitStateT (StateT FitState m a) deriving (Monad, MonadTrans)

在项目的某个时刻,我决定将 haskeline 添加到项目中,该项目具有如下所示的一些数据类型:

-- Stuff from haskeline.  MonadException is something that haskeline requires for whatever reason.
class MonadIO m => MonadException m
newtype InputT m a = InputT (m a) deriving (Monad, MonadIO)

所以我的主文件中的例程将如下所示:

myMainRoutineFunc :: (MonadException m, MonadIO m) => FitStateT (InputT m) ()
myMainRoutineFunc = do
myFitStateFunc
lift $ myInputFunc
return ()

不幸的是,随着我的程序的增长,出现了许多问题。主要问题是,对于我运行的每个输入函数,我都必须在运行它之前抬起它。另一个问题是对于运行输入命令的每个函数,我需要对其进行 MonadException m 约束。此外,对于任何运行 fitstate 相关函数的函数,它都需要 MonadIO m 约束。

代码如下:https://gist.github.com/4364920

因此,我决定创建一些类,使其更好地组合在一起,并稍微清理一下类型。我的目标是能够写出这样的东西:

myMainRoutineFunc :: (MonadInput t m, MonadFitState t m) => t m ()
myMainRoutineFunc = do
myFitStateFunc
myInputFunc
return ()

首先,我创建了一个 MonadInput 类来包装 InputT 类型,然后我自己的例程将成为该类的一个实例。

-- Stuff from haskeline.  MonadException is something that haskeline requires for whatever reason.
class MonadIO m => MonadException m
newtype InputT m a = InputT (m a) deriving (Monad, MonadIO)

-- So I add a new class MonadInput
class MonadException m => MonadInput t m where
liftInput :: InputT m a -> t m a

instance MonadException m => MonadInput InputT m where
liftInput = id

我添加了 MonadException 约束,这样我就不必在每个输入相关函数上单独指定它。这需要添加多参数类型类和灵活实例,但生成的代码正是我正在寻找的:

myInputFunc :: MonadInput t m => t m (Maybe String)
myInputFunc = liftInput $ undefined

然后我对 FitState 做了同样的事情。我再次添加了 MonadIO 约束:

-- Stuff from my own transformer.  This requires that m be MonadIO because it needs to store state to disk
data FitState = FitState
newtype FitStateT m a = FitStateT (StateT FitState m a) deriving (Monad, MonadTrans, MonadIO)

class MonadIO m => MonadFitState t m where
liftFitState :: FitStateT m a -> t m a

instance MonadIO m => MonadFitState FitStateT m where
liftFitState = id

这又完美地工作了。

myFitStateFunc :: MonadFitState t m => t m ()
myFitStateFunc = liftFitState $ undefined

然后我将主例程包装到一个新类型包装器中,以便我可以创建这两个类的实例:

newtype Routine m a = Routine (FitStateT (InputT m) a)
deriving (Monad, MonadIO)

然后是 MonadInput 的实例:

instance MonadException m => MonadInput Routine m where
liftInput = Routine . lift

工作完美。现在 MonadFitState:

instance MonadIO m => MonadFitState Routine m where
liftFitState = undefined
-- liftFitState = Routine -- This fails with an error.

哎呀,失败了。

Couldn't match type `m' with `InputT m'
`m' is a rigid type variable bound by
the instance declaration at Stack2.hs:43:18
Expected type: FitStateT m a -> Routine m a
Actual type: FitStateT (InputT m) a -> Routine m a
In the expression: Routine
In an equation for `liftFitState': liftFitState = Routine

我不知道该怎么做才能让这项工作成功。我真的不明白这个错误。这是否意味着我必须使 FitStateT 成为 MonadInput 的实例?这看起来真的很奇怪,这是两个完全不同的模块,没有任何共同点。任何帮助,将不胜感激。有没有更好的方法来获得我正在寻找的东西?

已完成的代码,但有错误:https://gist.github.com/4365046

最佳答案

首先,这是 liftFitState 的类型:

liftFitState :: MonadFitState t m => FitStateT m a -> t m a

这是例程的类型:

Routine :: FitStateT (InputT m) a -> Routine m a

您的 liftFitState 函数期望从 FitStateT 转换单个包装器类型,但 Routine 有两层它包装的转换器。所以类型不会匹配。

除此之外,我真的怀疑你的处理方式是错误的。

首先,如果您正在编写应用程序,而不是库,则更常见的做法是简单地将所需的所有 monad 转换器包装在一个大堆栈中并在任何地方使用它。通常,将其保留为变压器的唯一原因是在有限数量的基本单子(monad)之间进行切换,例如IdentityIOSTSTM。但如果您需要变压器堆栈的所有功能都需要 IO 并且您不打算使用 STSTM,那么即使这样也太过分了。

在你的情况下,最简单的方法显然是这样的:

newtype App a = App { getApp :: StateT FitState (InputT IO) a }

...然后派生或手动实现您想要的 MonadFoo 类(例如 MonadIO),并在任何地方简单地使用该堆栈。

这样做的好处是,以后如果您需要以添加 Haskeline 的方式添加另一个转换器,而不是浪费多个层,则决定添加一个 ReaderT例如,某种全局数据资源 - 您可以简单地将其添加到包装堆栈中,当前使用该堆栈的所有代码甚至不会知道其中的区别。

<小时/>

另一方面,如果您确实想采用当前的方法,那么您会发现 monad 变压器提升习惯用法有点错误。基本的提升操作应该来自您已经派生的 MonadTransMonadFoo 类通常用于为每个 monad 提供基本操作,例如MonadStategetput

您似乎正在尝试模仿 liftIO,这是一个“一路提升”操作,用于 lift 足够多次以从堆栈底部获取 - IO——到实际的 monad。对于可以出现在堆栈中任何位置的变压器来说,这实际上没有意义。

如果您确实想拥有自己的 MonadFoo 类,我建议您查看 MonadState 等类的源代码并了解它们是如何工作的,然后遵循相同的模式。

关于haskell - 如何将具有类约束的多个非标准变压器组合成一个堆栈?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14013538/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com