Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/17.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 如何将多个具有类约束的非标准转换器组合到一个堆栈中?_Haskell_Monad Transformers - Fatal编程技术网

Haskell 如何将多个具有类约束的非标准转换器组合到一个堆栈中?

Haskell 如何将多个具有类约束的非标准转换器组合到一个堆栈中?,haskell,monad-transformers,Haskell,Monad Transformers,这将是一个很长的过程,因为我不确定我是否以正确的心态进行了讨论,所以我将在每一步尽可能清晰地概述我的想法。我有两个代码片段,它们尽可能少,所以请随意使用 我从一个转换器FitStateT m a开始,它只保存程序当时的状态,并允许保存到磁盘: data FitState = FitState newtype FitStateT m a = FitStateT (StateT FitState m a) deriving (Monad, MonadTrans) 在项目的某个阶段,我决定将hask

这将是一个很长的过程,因为我不确定我是否以正确的心态进行了讨论,所以我将在每一步尽可能清晰地概述我的想法。我有两个代码片段,它们尽可能少,所以请随意使用

我从一个转换器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 ()
myMainRoutineFunc :: (MonadInput t m, MonadFitState t m) => t m ()
myMainRoutineFunc = do
  myFitStateFunc
  myInputFunc
  return ()
newtype App a = App { getApp :: StateT FitState (InputT IO) a }
不幸的是,随着我的程序的发展,这方面出现了许多问题。主要的问题是,对于我运行的每一个输入函数,我都必须在运行它之前提升它。另一个问题是,对于每个运行输入命令的函数,我都需要一个MonadException m约束。此外,对于运行fitstate相关函数的任何函数,它都需要MonadIO m约束

代码如下:

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

myMainRoutineFunc :: (MonadException m, MonadIO m) => FitStateT (InputT m) ()
myMainRoutineFunc = do
  myFitStateFunc
  lift $ myInputFunc
  return ()
myMainRoutineFunc :: (MonadInput t m, MonadFitState t m) => t m ()
myMainRoutineFunc = do
  myFitStateFunc
  myInputFunc
  return ()
newtype App a = App { getApp :: StateT FitState (InputT IO) a }
首先,我创建了一个MonadInput类来包装输入类型,然后我自己的例程将成为这个类的一个实例

-- 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约束,这样就不必在每个与输入相关的函数上单独指定它。这就需要添加MultiparamTypeClass和flexibleinstances,但生成的代码正是我想要的:

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的一个实例?这看起来很奇怪,这是两个完全不同的模块,没有任何共同之处。任何帮助都将不胜感激。有没有更好的方法来得到我想要的


已完成的代码有错误:

首先,这里是
liftFitState的类型:

liftFitState :: MonadFitState t m => FitStateT m a -> t m a
下面是
例程的类型:

Routine :: FitStateT (InputT m) a -> Routine m a
您的
liftFitState
函数要求从
FitStateT
转换单个包装类型,但
例程有两层它包装的转换器。所以类型不匹配

除此之外,我真的怀疑你走错了方向

首先,如果你正在编写一个应用程序,而不是一个库,那么更常见的做法是将你需要的所有monad转换器封装在一个大堆栈中,并在任何地方使用。通常,将其作为转换器的唯一原因是在有限数量的基单子之间切换,例如
标识
IO
ST
,或
STM
。但是,如果您需要变压器堆栈的所有东西都需要IO,并且您不打算使用
ST
STM
,那么即使这样也太过分了

在您的案例中,最简单的方法显然如下所示:

myMainRoutineFunc :: (MonadException m, MonadIO m) => FitStateT (InputT m) ()
myMainRoutineFunc = do
  myFitStateFunc
  lift $ myInputFunc
  return ()
myMainRoutineFunc :: (MonadInput t m, MonadFitState t m) => t m ()
myMainRoutineFunc = do
  myFitStateFunc
  myInputFunc
  return ()
newtype App a = App { getApp :: StateT FitState (InputT IO) a }
…然后派生或手动实现所需的
MonadFoo
类(例如
MonadIO
),只需到处使用该堆栈即可

这样做的好处是,以后如果您需要像添加Haskeline那样添加另一个转换器,而不是在多个层上乱搞,那么决定为某种全局数据资源添加一个
ReaderT
,比方说——您可以简单地将它添加到包装的堆栈中,而当前使用该堆栈的所有代码甚至都不知道它们之间的区别


另一方面,如果您真的想采用当前的方法,那么monad transformer提升习惯用法就有点错误了。基本的提升操作应该来自您已经推导的
MonadTrans
MonadFoo
类通常用于为每个monad提供基本操作,例如
get
put
用于
MonadState

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


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

虽然这不是一个库,但我的问题是这个“例程”实际上是一个举重例程。我有大约5种不同的习惯,我想试试。现在前端是一个控制台程序,但我没有理由不在某个时候制作一个web前端。所以我有一个强烈的愿望,就是尽可能地将所有的内部构件分开,这样我就可以混合和匹配它们。我在liftIO之后做了这个模型,因为我不知道还有什么其他方法可以做。MonadState/Reader类似乎也有同样的问题?@mindreader IIRC,对于
MonadState
,您可以通过为堆栈中最外层的转换器定义一个实例(或派生它)来解决这个问题,然后将get、put等操作提升到适当的级别。