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

发布于 2025-01-18 17:25:13 字数 940 浏览 3 评论 0原文

真的不知道这里发生了什么。我不是 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

暖心男生 2025-01-25 17:25:13

这就是单态限制。

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

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

let log = getDebugPrint appData :: Show a => PrintLevel -> a -> IO ()

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 的类型签名中,而不是表达式的类型注释中(无论如何,这确实是您有兴趣修复类型的原因),就像这样:

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

至于为什么你以前从未遇到过这个问题,你应该能够从我上面的描述中看到,必须有几个因素才能导致这个问题:

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

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


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

x :: Int
x = 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 = getDebugPrint appData :: Show a => PrintLevel -> a -> IO ()

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 expression getDebugPrint 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.1

So 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 infer Show a => PrintLevel -> a -> IO (); since the type variable a is constrained by Show a it must be instantiated to a specific concrete type. It looks at the places where you have used log to pick one, so if you had only used it at a single type everything would have worked fine. But the first call log Log "My Game" requires it to pick String and the second requires it to pick AppData. Apparently it picked String 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:

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

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:

  1. If you only use the binding at a single type, the compiler infers the correct monomorphic type based on usage anyway.
  2. If the type variable that is being used at multiple types doesn't have a class constraint, the monomorphism restriction doesn't apply.
  3. If you have explicit syntactic parameters in your binding, the monomorphism restriction doesn't apply.
  4. In GHCi the default is for the 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:

x :: Int
x = 1

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 type Num a => a.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文