在 do 块中对两种不同类型调用多态函数时出现类型错误

Lia*_*nne 4 polymorphism haskell

真的不知道这里发生了什么。我不是 Haskell 新手

\n

我只是想调用我在两种不同类型上定义的多态函数,编译器会抱怨第二个函数调用,因为该类型与我第一次调用它时不同。

\n
main :: IO ()\nmain = do\n    appData <- initializeAppData\n    let log = getDebugPrint appData :: Show a => PrintLevel -> a -> IO ()\n    log Log "My Game"\n    log Debug appData\n
Run Code Online (Sandbox Code Playgroud)\n

我收到以下错误

\n
/home/lianne/game/my-game/app/Main.hs:12:15: error:\n\xe2\x80\xa2 Couldn't match type \xe2\x80\x98AppData\xe2\x80\x99 with \xe2\x80\x98[Char]\xe2\x80\x99\n  Expected: String\n    Actual: AppData\n\xe2\x80\xa2 In the second argument of \xe2\x80\x98log\xe2\x80\x99, namely \xe2\x80\x98appData\xe2\x80\x99\n  In a stmt of a 'do' block: log Debug appData\n  In the expression:\n    do appData <- initializeAppData\n       let log = ...\n       log Log "My Game"\n       log Debug appData\n
Run Code Online (Sandbox Code Playgroud)\n

如果我切换最后两行,以便它记录appData之前的“我的游戏”,那么它会显示预期的 AppData,而实际类型是 String。

\n

为了清楚起见,我添加了 let 上的类型定义,看看它是否会强制多态性。该getDebugPrint函数也有类型定义。

\n

我正在使用堆栈和 ghc 8.8.1

\n

是因为它是在 do 块中定义的吗?

\n

Ben*_*Ben 6

这就是单态限制。

有关单态限制的更详细讨论,请参阅它的优秀标准问题:什么是单态限制?

但或多或少它说:如果定义的绑定没有任何语法参数(无论它的类型是否表明它有参数),并且绑定没有显式类型签名,那么任何具有类型类约束的类型变量都必须是解析为单一类型(满足约束)。

let log = getDebugPrint appData :: Show a => PrintLevel -> a -> IO ()
Run Code Online (Sandbox Code Playgroud)

let log = ...从语法上讲,是一个简单的变量绑定,没有为参数指定显式名称。所以这个条件适用。

但是“无显式类型签名”规则又如何呢?你在那里写了你想要的 type Show a => PrintLevel -> a -> IO (),那么什么给出呢?

您编写的方式实际上并未提供变量的类型签名log。相反,您为表达式提供类型注释getDebugPrint appData,这不是一回事。log可以给出作为右侧类型实例化的任何类型,它不必完全相同。1

因此,由于您没有给出变量本身的类型log,编译器必须推断出一个类型。这样做时,它将应用单态限制,这意味着它无法推断Show a => PrintLevel -> a -> IO (); 由于类型变量a受到限制,因此Show a必须将其实例化为特定的具体类型。它会查看您曾经 选择过的位置log,因此,如果您仅在单一类型上使用它,那么一切都会正常工作。但第一个调用log Log "My Game"要求它进行 pick String,第二个调用则要求它进行 pick AppData。显然它选择了String然后抱怨另一个呼叫不匹配;我不确定它是否总是根据第一次使用进行选择,或者是否是任意的,但这并不重要。

修复它的最简单方法是将您编写的类型放入类型签名中,log而不是表达式的类型注释中(无论如何,这确实是您有兴趣修复类型的原因),如下所示:

main :: IO ()
main = do
    appData <- initializeAppData
    let log :: Show a => PrintLevel -> a -> IO ()
        log = getDebugPrint appData
    log Log "My Game"
    log Debug appData
Run Code Online (Sandbox Code Playgroud)

至于为什么你以前从未遇到过这个问题,从我上面的描述中你应该可以看出,造成这个问题的因素有以下几个:

  1. 如果您仅在单一类型上使用绑定,则编译器无论如何都会根据使用情况推断出正确的单态类型。
  2. 如果在多种类型中使用的类型变量没有类约束,则单态性限制不适用。
  3. 如果您的绑定中有显式语法参数,则单态限制不适用。
  4. 在 GHCi 中,默认情况下NoMonomorphismRestriction扩展处于活动状态(从 GHC 8 左右开始,IIRC),这会禁用单态限制。因此,您在解释器中尝试的任何操作都不会出现此问题。

因此,这很可能是该编码模式第一次同时触发所有条件。


1如果您认为这很愚蠢,请考虑以下内容:

x :: Int
x = 1
Run Code Online (Sandbox Code Playgroud)

如果您无法在右侧定义一个具有更通用表达式的特定类型变量(就像1type 一样) ,那么这将是无效的Num a => a