在 do 块中对两种不同类型调用多态函数时出现类型错误
真的不知道这里发生了什么。我不是 Haskell 新手,
我只是想调用我在两种不同类型上定义的多态函数,编译器抱怨第二个函数调用,因为该类型与我第一次调用它时不同。
main :: IO ()
main = do
appData <- initializeAppData
let log = getDebugPrint appData :: Show a => PrintLevel -> a -> IO ()
log Log "My Game"
log Debug appData
我收到以下错误
/home/lianne/game/my-game/app/Main.hs:12:15: error:
• Couldn't match type ‘AppData’ with ‘[Char]’
Expected: String
Actual: AppData
• In the second argument of ‘log’, namely ‘appData’
In a stmt of a 'do' block: log Debug appData
In the expression:
do appData <- initializeAppData
let log = ...
log Log "My Game"
log Debug appData
如果我切换最后两行,以便它在“我的游戏”之前记录 appData
,那么它会显示预期的 AppData,而实际类型是 String。
为了清楚起见,我添加了 let 上的类型定义,看看它是否会强制多态性。 getDebugPrint
函数也有一个类型定义。
我正在使用 stack 和 ghc 8.8.1
是因为它是在 do 块中定义的吗?
Really not sure what is happening here. I'm not a Haskell newbee
I'm just trying to call a polymorphic function that I define on two different types and the compiler complains about the second function call because the type is different than the first time I called it.
main :: IO ()
main = do
appData <- initializeAppData
let log = getDebugPrint appData :: Show a => PrintLevel -> a -> IO ()
log Log "My Game"
log Debug appData
I'm getting the following error
/home/lianne/game/my-game/app/Main.hs:12:15: error:
• Couldn't match type ‘AppData’ with ‘[Char]’
Expected: String
Actual: AppData
• In the second argument of ‘log’, namely ‘appData’
In a stmt of a 'do' block: log Debug appData
In the expression:
do appData <- initializeAppData
let log = ...
log Log "My Game"
log Debug appData
If I switch the last two lines so it logs the appData
before "My Game" then it says expected AppData and the actual type is String.
The type definition on the let I added for clarity and see if it would force the polymorphism. The getDebugPrint
function has a type definition too.
I'm using stack and ghc 8.8.1
Is it because it is being defined in a do block?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
这就是单态限制。
有关单态限制的更详细讨论,请参阅它的优秀标准问题:什么是单态限制?
但或多或少它说的是:如果一个绑定定义时没有任何语法参数(无论它的类型是否表明它有参数),并且没有显式的绑定的类型签名,则任何具有类型类约束的类型变量都必须解析为单一类型(满足约束)。
let log = ...
在语法上是一个简单的变量绑定,没有为参数指定显式名称。所以这个条件适用。但是“无显式类型签名”规则又如何呢?您在那里写道您想要类型
Show a =>;打印级别->一个-> IO()
,那么给出了什么?您编写的方式实际上没有为变量
log
提供类型签名。相反,您为表达式 getDebugPrint appData 提供类型注释,这不是同一件事。log
可以被赋予作为右侧类型实例化的任何类型,它不必完全相同。1因此,既然您没有给出变量
log
本身的类型,编译器必须推断出一个类型。这样做时,它将应用单态限制,这意味着它不能推断Show a =>;打印级别->一个-> IO()
;由于类型变量a
受到Show a
的约束,因此它必须实例化为特定的具体类型。它会查看您使用log
来选择一个的位置,因此,如果您仅在单一类型上使用它,那么一切都会正常工作。但第一个调用log Log "My Game"
要求它选择String
,第二个调用要求它选择AppData
。显然它选择了String
,然后抱怨另一个调用不匹配;我不确定它是否总是根据第一次使用进行选择,或者是否是任意的,但这并不重要。修复它的最简单方法是将您编写的类型放入
log
的类型签名中,而不是表达式的类型注释中(无论如何,这确实是您有兴趣修复类型的原因),就像这样:至于为什么你以前从未遇到过这个问题,你应该能够从我上面的描述中看到,必须有几个因素才能导致这个问题:
NoMonomorphismRestriction
扩展处于活动状态(从 GHC 8 左右开始,IIRC),这会禁用单态限制。因此,您在解释器中尝试的任何操作都不会出现此问题。因此,这很可能是该编码模式第一次同时触发所有条件。
1 如果您认为这很愚蠢,请考虑一下:
如果您无法在右侧使用更通用的表达式来定义特定类型的变量,则该变量将无效,如
1
自然具有类型Num a =>;一个。
It's the monomorphism restriction.
For more detailed discussion of the monomorphism restriction in general, see the excellent standard question for it: What is the monomorphism restriction?
But more or less it says: if a binding is defined without any syntactic parameters (regardless of whether it's type indicates it has parameters), and there's no explicit type signature for the binding, then any type variables with a type class constraint have to be resolved to a single type (that satisfies the constraint).
let log = ...
is syntactically a simple variable binding, with no explicit names given to parameters. So that condition applies.But what about the "no explicit type signature" rule? You wrote right there that you wanted the type
Show a => PrintLevel -> a -> IO ()
, so what gives?The way you have written it actually doesn't provide a type signature for the variable
log
. Rather you are providing a type annotation for the expressiongetDebugPrint appData
, which is not the same thing.log
could be given any type that is an instantiation of the right hand side's type, it doesn't have to be exactly the same.1So since you haven't given a type for the variable
log
itself, the compiler has to infer one. And in so doing it will apply the monomorphism restriction, which means it can't inferShow a => PrintLevel -> a -> IO ()
; since the type variablea
is constrained byShow a
it must be instantiated to a specific concrete type. It looks at the places where you have usedlog
to pick one, so if you had only used it at a single type everything would have worked fine. But the first calllog Log "My Game"
requires it to pickString
and the second requires it to pickAppData
. Apparently it pickedString
and then complained about the other call not matching; I'm unsure whether it always picks based on the first usage or whether it's arbitrary, but it doesn't really matter.The simplest way to fix it is to put the type you wrote into a type signature for
log
rather than a type annotation for the expression (that's really what you're interested in fixing the type for anyway), like so:As for why you've never had this problem before, you should be able to see from my description above that several factors had to line up for this to be a problem:
NoMonomorphismRestriction
extension to be active (since GHC 8 or so, IIRC), which disables the monomorphism restriction. So anything you try in the interpreter isn't going to have this issue.So it's quite plausible this is simply the first time that this coding pattern has triggered all of the conditions at once.
1 If you think that's dumb, consider this:
That wouldn't be valid if you couldn't define a specifically-typed variable with a more general expression on the right hand side, as
1
naturally has typeNum a => a
.