一个函数中两个多态类的解释器

问题描述 投票:0回答:1

我有这个多态代码(请参阅this question),其中包含用于模型和客户端的通用monad:

import Control.Monad.Writer

class Monad m => Model m where
  act :: Client c => String -> c a -> m a

class Monad c => Client c where
  addServer :: String -> c ()

scenario1 :: forall c m. (Client c, Model m) => m ()
scenario1 = do
  act "Alice" $ addServer @c "https://example.com"

这是Client的漂亮印刷解释器,它通过Writer monad解释日志中的动作:

type Printer = Writer [String]

instance Client Printer where
  addServer :: String -> Printer ()
  addServer srv = tell ["  add server " ++ srv ++ "to the client"]

Model的翻译很困难。我尝试了几件事,每件事都会导致自己的错误:

  1. “无法匹配类型'c'”:
instance Model Printer where
  act :: String -> Printer a -> Printer a
  act name action = do
    tell [name ++ ":"]
    action
  1. “`无法将类型为'打印机a'的表达式应用于可见类型参数'(打印机a)'”]
instance Model Printer where
  act :: forall a. String -> Printer a -> Printer a
  act name action = do
    tell [name ++ ":"]
    action @(Printer a)
  1. “无法将类型'c'与'WriterT [String] Data.Functor.Identity.Identity'”
instance Model Printer where
  act :: Client c => String -> c a -> Printer a
  act name action = do
    tell [name ++ ":"]
    action

某种程度上,我需要说c a中的act现在是Printer a

也许我需要在Model类中有两个参数-Model monad的m和Client monad的c,并且Model类还应该定义函数clientToModel :: c a -> m a

是否有办法使模型和客户端脱钩?我可能仍需要每对clientToModel :: c a -> m a吗?

我感谢您的建议。谢谢!

haskell polymorphism monads typeclass tagless-final
1个回答
0
投票

问题是act的类型签名保证它可以在any客户端上运行,但是在这里,您试图限制它仅在名为Printer的特定客户端上运行。这违反了Model类型类的定义。


您显然想遵循的通常模式是在同一个monad上同时定义ModelClient,如下所示:

class Monad m => Model m where
  act :: String -> m a -> m a

class Monad m => Client m where
  addServer :: String -> m ()

[这具有很好的,易于理解的语义,即actaddServer都是“环境上下文”操作,“在monad m中可用”。它们几乎就像“全局函数”,但仍然是可模拟的。

然后Printer可能是这种单子的一个示例,同时实现ClientModel。然后您的生产堆栈-例如ReaderT Config IO或您拥有的任何东西-可能是此类monad的另一个示例。


但是,如果您坚持在不同的单子上定义ModelClient,则使类型起作用的唯一方法是将Client c约束从act的签名提升为[[ C0]类别:

Model

其含义是“ 每个“模型”单子都与一组特定的“客户端”单子一起工作,而不仅仅是随机的“客户端”单子。]]。

然后您可以这样定义class (Monad m, Client c) => Model m c where act :: String -> c a -> m a 实例:

Printer

并且类型将起作用。


话虽如此,我想再次重申,您决定在不同单子上定义instance Model Printer Printer where act name action = do tell [name ++ ":"] action Client的决定对我来说是一种气味。我强烈建议您按照上述建议重新考虑设计。

© www.soinside.com 2019 - 2024. All rights reserved.