一个函数中的两个多态 类
Two polymorphic classes in one function
我有这个带有 State monads 的代码:
import Control.Monad.State
data ModelData = ModelData String
data ClientData = ClientData String
act :: String -> State ClientData a -> State ModelData a
act _ action = do
let (result, _) = runState action $ ClientData ""
return result
addServer :: String -> State ClientData ()
addServer _ = return ()
scenario1 :: State ModelData ()
scenario1 = do
act "Alice" $ addServer "https://example.com"
我正在尝试使用多态类型来概括它-类 按照这种方法:https://serokell.io/blog/tagless-final.
我可以概括 ModelData:
import Control.Monad.State
class Monad m => Model m where
act :: String -> State c a -> m a
data Client = Client String
addServer :: String -> State Client ()
addServer _ = return ()
scenario1 :: Model m => m ()
scenario1 = do
act "Alice" $ addServer "https://example.com"
但是当我尝试同时使用 ModelData 和 ClientData 时,编译失败:
module ExampleFailing where
class Monad m => Model m where
act :: Client c => String -> c a -> m a
class Monad c => Client c where
addServer :: String -> c ()
scenario1 :: Model m => m ()
scenario1 = do
act "Alice" $ addServer "https://example.com"
错误:
• Could not deduce (Client c0) arising from a use of ‘act’
from the context: Model m
bound by the type signature for:
scenario1 :: forall (m :: * -> *). Model m => m ()
at src/ExampleFailing.hs:9:1-28
The type variable ‘c0’ is ambiguous
• In the expression: act "Alice"
In a stmt of a 'do' block:
act "Alice" $ addServer "https://example.com"
In the expression:
do act "Alice" $ addServer "https://example.com"
|
11 | act "Alice" $ addServer "https://example.com"
| ^^^^^^^^^^^
我可以这样编译它,但它似乎与我试图概括的原始代码不同:
{-# LANGUAGE MultiParamTypeClasses #-}
module ExamplePassing where
class Monad m => Model m c where
act :: Client c => String -> c a -> m (c a)
class Monad c => Client c where
addServer :: String -> c ()
scenario1 :: (Client c, Model m c) => m (c ())
scenario1 = do
act "Alice" $ addServer "https://example.com"
非常感谢您的建议。谢谢!
您对 act :: Client c => String -> c a -> m a
的泛化尝试在技术上是正确的:它实际上是原始代码的翻译,但是将 State ModelData
替换为 m
并将 State ClientData
替换为 c
.
错误发生是因为现在 "client" 可以是任何东西,scenario1
的调用者无法指定它应该是什么。
你看,为了确定要调用哪个版本的 addServer
,编译器必须知道 c
是什么,但无处可推断! c
既不出现在函数参数中,也不出现在 return 类型中。所以从技术上讲,它绝对可以是任何东西,它完全隐藏在 scenario1
中。但是 "absolutely anything" 对编译器来说不够好,因为 c
的选择决定了调用哪个版本的 addServer
,这将决定程序的行为。
这是同一问题的简化版本:
f :: String -> String
f str = show (read str)
这同样不会编译,因为编译器不知道要调用哪个版本的 show
和 read
。
你有几个选择。
首先,如果scenario1
本身知道使用哪个客户端,它可以使用TypeApplications
:
这样说
scenario1 :: Model m => m ()
scenario1 = do
act "Alice" $ addServer @(State ClientData) "https://example.com"
其次,scenario1
可以将这个任务卸载给任何调用它的人。为此,您需要声明一个通用变量 c
,即使它没有出现在任何参数或参数中。这可以通过 ExplicitForAll
:
来完成
scenario1 :: forall c m. (Client c, Model m) => m ()
scenario1 = do
act "Alice" $ addServer @c "https://example.com"
(请注意,您仍然需要执行 @c
让编译器知道要使用哪个版本的 addServer
;要做到这一点,您需要 ScopedTypeVariables
, 其中包括 ExplicitForAll
)
那么消费者将不得不做这样的事情:
let server = scenario1 @(State ClientData)
最后,如果由于某种原因你不能使用TypeApplications
、ExplicitForAll
或ScopedTypeVariables
,你可以做穷人的版本同样的事情——使用一个额外的虚拟参数来引入类型变量(这是以前的做法):
class Monad c => Client c where
addServer :: Proxy c -> String -> c ()
scenario1 :: (Client c, Model m) => Proxy c -> m ()
scenario1 proxyC = do
act "Alice" $ addServer proxyC "https://example.com"
(注意 class 方法本身现在也获得了一个虚拟参数;否则将无法再次调用它)
那么消费者将不得不做这件丑陋的事情:
let server = scenario1 (Proxy :: Proxy (State ClientData))
我有这个带有 State monads 的代码:
import Control.Monad.State
data ModelData = ModelData String
data ClientData = ClientData String
act :: String -> State ClientData a -> State ModelData a
act _ action = do
let (result, _) = runState action $ ClientData ""
return result
addServer :: String -> State ClientData ()
addServer _ = return ()
scenario1 :: State ModelData ()
scenario1 = do
act "Alice" $ addServer "https://example.com"
我正在尝试使用多态类型来概括它-类 按照这种方法:https://serokell.io/blog/tagless-final.
我可以概括 ModelData:
import Control.Monad.State
class Monad m => Model m where
act :: String -> State c a -> m a
data Client = Client String
addServer :: String -> State Client ()
addServer _ = return ()
scenario1 :: Model m => m ()
scenario1 = do
act "Alice" $ addServer "https://example.com"
但是当我尝试同时使用 ModelData 和 ClientData 时,编译失败:
module ExampleFailing where
class Monad m => Model m where
act :: Client c => String -> c a -> m a
class Monad c => Client c where
addServer :: String -> c ()
scenario1 :: Model m => m ()
scenario1 = do
act "Alice" $ addServer "https://example.com"
错误:
• Could not deduce (Client c0) arising from a use of ‘act’
from the context: Model m
bound by the type signature for:
scenario1 :: forall (m :: * -> *). Model m => m ()
at src/ExampleFailing.hs:9:1-28
The type variable ‘c0’ is ambiguous
• In the expression: act "Alice"
In a stmt of a 'do' block:
act "Alice" $ addServer "https://example.com"
In the expression:
do act "Alice" $ addServer "https://example.com"
|
11 | act "Alice" $ addServer "https://example.com"
| ^^^^^^^^^^^
我可以这样编译它,但它似乎与我试图概括的原始代码不同:
{-# LANGUAGE MultiParamTypeClasses #-}
module ExamplePassing where
class Monad m => Model m c where
act :: Client c => String -> c a -> m (c a)
class Monad c => Client c where
addServer :: String -> c ()
scenario1 :: (Client c, Model m c) => m (c ())
scenario1 = do
act "Alice" $ addServer "https://example.com"
非常感谢您的建议。谢谢!
您对 act :: Client c => String -> c a -> m a
的泛化尝试在技术上是正确的:它实际上是原始代码的翻译,但是将 State ModelData
替换为 m
并将 State ClientData
替换为 c
.
错误发生是因为现在 "client" 可以是任何东西,scenario1
的调用者无法指定它应该是什么。
你看,为了确定要调用哪个版本的 addServer
,编译器必须知道 c
是什么,但无处可推断! c
既不出现在函数参数中,也不出现在 return 类型中。所以从技术上讲,它绝对可以是任何东西,它完全隐藏在 scenario1
中。但是 "absolutely anything" 对编译器来说不够好,因为 c
的选择决定了调用哪个版本的 addServer
,这将决定程序的行为。
这是同一问题的简化版本:
f :: String -> String
f str = show (read str)
这同样不会编译,因为编译器不知道要调用哪个版本的 show
和 read
。
你有几个选择。
首先,如果scenario1
本身知道使用哪个客户端,它可以使用TypeApplications
:
scenario1 :: Model m => m ()
scenario1 = do
act "Alice" $ addServer @(State ClientData) "https://example.com"
其次,scenario1
可以将这个任务卸载给任何调用它的人。为此,您需要声明一个通用变量 c
,即使它没有出现在任何参数或参数中。这可以通过 ExplicitForAll
:
scenario1 :: forall c m. (Client c, Model m) => m ()
scenario1 = do
act "Alice" $ addServer @c "https://example.com"
(请注意,您仍然需要执行 @c
让编译器知道要使用哪个版本的 addServer
;要做到这一点,您需要 ScopedTypeVariables
, 其中包括 ExplicitForAll
)
那么消费者将不得不做这样的事情:
let server = scenario1 @(State ClientData)
最后,如果由于某种原因你不能使用TypeApplications
、ExplicitForAll
或ScopedTypeVariables
,你可以做穷人的版本同样的事情——使用一个额外的虚拟参数来引入类型变量(这是以前的做法):
class Monad c => Client c where
addServer :: Proxy c -> String -> c ()
scenario1 :: (Client c, Model m) => Proxy c -> m ()
scenario1 proxyC = do
act "Alice" $ addServer proxyC "https://example.com"
(注意 class 方法本身现在也获得了一个虚拟参数;否则将无法再次调用它)
那么消费者将不得不做这件丑陋的事情:
let server = scenario1 (Proxy :: Proxy (State ClientData))