0

我有这个多态代码(见这个问题),带有用于模型和客户端的通用单子:

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"

这是通过 Writer monad 解释日志中操作的漂亮打印解释器Client

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. “`不能将'Printer a'类型的表达式应用于可见类型参数'(Printer 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是什么。actPrinter a

也许我需要在模型类中有两个参数——m模型单子和c客户端单子,模型类也应该定义函数clientToModel :: c a -> m a

有没有办法让模型和客户端解耦?我可能还需要clientToModel :: c a -> m a每一对?

我很感激这个建议。谢谢!

4

1 回答 1

1

问题是act承诺的类型签名可以在任何客户端上工作,但在这里您试图将其限制为仅在名为Printer. 这违反了Model类型类的定义。


您显然试图遵循的通常模式是在同一个 monad 上同时定义Model和定义,如下所示:Client

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

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

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

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


但是,如果您坚持Model并被Client定义在不同的 monad 上,那么使类型起作用的唯一方法是将Client c约束从签名的签名提升act到类的签名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

并且类型将起作用。


话虽如此,我想再次重申,您对不同 monad 进行定义的决定对我来说是一种气味ClientModel我强烈建议您按照上面的建议重新考虑您的设计。

于 2020-03-22T21:09:07.280 回答