Haskell 混合交换单子

Haskell 混合交换单子,haskell,Haskell,我一直在阅读无标签的最终编码,这给了我一个关于mtl的新视角。也就是说,具体的monad堆栈为dsl指定了一个解释器。这使我更清楚地知道为什么需要n^2个实例 当然,也有一些交换单子,它们不需要这样做。最值得注意的用例是分解IO,以便更容易进行推理和测试 我的第一个想法是通过一个实现对MonadIO进行参数化: class (Monad m, Monad (Eff m)) => MonadEff m where type Eff m :: (* -> *) liftE

我一直在阅读无标签的最终编码,这给了我一个关于mtl的新视角。也就是说,具体的monad堆栈为dsl指定了一个解释器。这使我更清楚地知道为什么需要n^2个实例

当然,也有一些交换单子,它们不需要这样做。最值得注意的用例是分解IO,以便更容易进行推理和测试

我的第一个想法是通过一个实现对MonadIO进行参数化:

class (Monad m, Monad (Eff m)) => MonadEff m where
    type Eff m :: (* -> *)
    liftEff :: Eff m a -> m a
令人惊讶的是,这似乎很好。它允许定义一次提升实例

instance MonadEff IO where
  type Eff IO = IO
  liftEff = id
instance MonadEff Tracer where
  type Eff Tracer = Tracer
  liftEff = id
instance MonadEff m => MonadEff (ExceptT e m) where
  type Eff (ExceptT a m) = Eff m
  liftEff = lift . liftEff
-- ...
并轻松合成效果:

g :: (MonadEff m, MonadError String m, ConsoleEff (Eff m)) => m ()
g = liftEff (put "foo") >> throwError "bar"

h :: (MonadEff m, ConsoleEff (Eff m), FileEff (Eff m)) => m ()
h = liftEff $ do
  l1 <- get
  l2 <- get
  c <- read l2
  put $  c ++ l1

class FileEff repr where
  read :: FilePath -> repr String
  write :: FilePath -> String -> repr ()
class ConsoleEff repr where
  put :: String -> repr ()
  get :: repr String
instance ConsoleEff IO where
  get  = getLine
  put s = putStrLn s
instance ConsoleEff Tracer where
  put s = W.tell [s] >> return ()
  get = do
    i <- S.get
    S.put (i+1)
    W.tell $ ["putL '" ++ show i ++ "'"]
    return $ "$" ++ show i ++ "(get)"
因此,乍一看,这似乎是完美的,而只需要无害的语言扩展和适合现有的mtl基础设施。不过,我以前从未见过这种情况,所以我假设这一切都有一个陷阱


问题是什么?

我看不出这对多层有什么帮助。例如,您如何指定参数
m
能够引发异常,而无需拼写
ExceptT
层?您的
FileEff
ConsoleEff
实例是什么样的?我怀疑您必须为每个
repr
重写它们。MonadEff类型只会替换底层。您还必须为每个repr重写FileEff和ConsoleEff。有用的是,您可以在重用MonadEff时添加嵌入monad transformer的新效果。因此
(MonadError字符串m、MonadEff m、FileEff(Eff m))=>m()
可以是
任意字符串(IO())
任意字符串(Tracer())
等等。它还允许在不重新实现所有IO的情况下对ConsoleEff进行单独的实现。我在我的问题中添加了ConsoleEff的示例实现。您可以尝试将其实现为一个库,支持常见的
阅读器
状态
,等等。可以使用
mtl
进行基准测试,看看您的表现如何。如果一切都很好,那么在Reddit上发布并获得一些合理的评论:)
exec :: IO a -> IO a
exec = id
trace :: Tracer a -> [String]
trace (Tracer f) = snd . runWriter $ evalStateT f (0::Int)