一个函数中两个多态 类 的解释器
Interpreters for two polymorphic classes in one function
我有这个多态代码(见 ),带有模型和客户端的通用单子:
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
的解释器很难。我尝试了几件事,每件事都会导致自己的错误:
- "Couldn't match type ‘c’":
instance Model Printer where
act :: String -> Printer a -> Printer a
act name action = do
tell [name ++ ":"]
action
- “`无法将‘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)
- "Couldn't match type ‘c’ with ‘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
不知何故我需要告诉 act
中的 c a
现在是 Printer a
。
也许我需要在模型 class 中有两个参数 - m
用于模型 monad 和 c
用于客户端 monad,模型 class 也应该定义函数 clientToModel :: c a -> m a
?
有没有办法让模型和客户端解耦?我可能还需要每对 clientToModel :: c a -> m a
?
感谢您的建议。谢谢!
问题是 act
的类型签名承诺它可以在 任何 客户端上工作,但在这里你试图限制它只在 上工作特定客户端称为 Printer
。这违反了 Model
类型的定义 class.
您显然试图遵循的通常模式是在同一个 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
都是 "ambient context" 操作,它们是 "available in the monad m
",这具有很好的、易于理解的语义。他们几乎像 "global functions",但仍然可以嘲笑。
那么 Printer
可能是这种 monad 的一个例子,实现了 Client
和 Model
。然后你的生产堆栈——比如 ReaderT Config IO
或你拥有的任何东西——可能是这种 monad 的另一个例子。
但是,如果您坚持在不同的 monad 上定义 Model
和 Client
,则使这些类型起作用的唯一方法是解除 Client c
签名中的约束act
到 Model
的签名 class:
class (Monad m, Client c) => Model m c where
act :: String -> c a -> m a
这意味着“每个 "model" monad 与一组特定的 "client" monad 一起工作,但不仅仅是任何随机的 "client" monad".
然后你可以这样定义 Printer
实例:
instance Model Printer Printer where
act name action = do
tell [name ++ ":"]
action
这些类型会起作用。
话虽如此,我想再次重申,您决定在不同的 monad 上定义 Client
和 Model
对我来说是一种异味。我强烈建议您按照上面的建议重新考虑您的设计。
我有这个多态代码(见
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
的解释器很难。我尝试了几件事,每件事都会导致自己的错误:
- "Couldn't match type ‘c’":
instance Model Printer where
act :: String -> Printer a -> Printer a
act name action = do
tell [name ++ ":"]
action
- “`无法将‘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)
- "Couldn't match type ‘c’ with ‘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
不知何故我需要告诉 act
中的 c a
现在是 Printer a
。
也许我需要在模型 class 中有两个参数 - m
用于模型 monad 和 c
用于客户端 monad,模型 class 也应该定义函数 clientToModel :: c a -> m a
?
有没有办法让模型和客户端解耦?我可能还需要每对 clientToModel :: c a -> m a
?
感谢您的建议。谢谢!
问题是 act
的类型签名承诺它可以在 任何 客户端上工作,但在这里你试图限制它只在 上工作特定客户端称为 Printer
。这违反了 Model
类型的定义 class.
您显然试图遵循的通常模式是在同一个 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
都是 "ambient context" 操作,它们是 "available in the monad m
",这具有很好的、易于理解的语义。他们几乎像 "global functions",但仍然可以嘲笑。
那么 Printer
可能是这种 monad 的一个例子,实现了 Client
和 Model
。然后你的生产堆栈——比如 ReaderT Config IO
或你拥有的任何东西——可能是这种 monad 的另一个例子。
但是,如果您坚持在不同的 monad 上定义 Model
和 Client
,则使这些类型起作用的唯一方法是解除 Client c
签名中的约束act
到 Model
的签名 class:
class (Monad m, Client c) => Model m c where
act :: String -> c a -> m a
这意味着“每个 "model" monad 与一组特定的 "client" monad 一起工作,但不仅仅是任何随机的 "client" monad".
然后你可以这样定义 Printer
实例:
instance Model Printer Printer where
act name action = do
tell [name ++ ":"]
action
这些类型会起作用。
话虽如此,我想再次重申,您决定在不同的 monad 上定义 Client
和 Model
对我来说是一种异味。我强烈建议您按照上面的建议重新考虑您的设计。