当前位置: 首页 > 知识库问答 >
问题:

当我指定x具有类型a时,为什么Haskell试图推断它具有类型A0?

朱睿
2023-03-14

有时我会在签名中指定某种类型,例如a,而GHC将响应它无法推断其类型为A0。发生这种情况的原因是单一的,还是多种不同的可能原因?有时我解决,有时不解决;我希望有一个统一的理论。

这里有一个简短的例子。(要查看这段代码,包括解释它试图做什么的注释,请参阅此处。)

{-# LANGUAGE MultiParamTypeClasses
           , AllowAmbiguousTypes
           , FlexibleInstances
           , GADTs #-}

type SynthName = String

data Synth format where
  Synth :: SynthName -> Synth format

data MessageA format where
  MessageA :: String -> MessageA format
data MessageB format where
  MessageB :: String -> MessageB format

class (Message format) a where
  theMessage :: a -> String
instance (Message format) (MessageA format) where
  theMessage (MessageA msg) = msg
instance (Message format) (MessageB format) where
  theMessage (MessageB msg) = msg

play :: Message format m => Synth format -> m -> IO ()
play (Synth name) msg =
  print $ name ++ " now sounds like " ++ theMessage msg

这会产生以下错误。

riddles/gadt-forget/closest-to-vivid.hs:38:42: error:
    • Could not deduce (Message format0 m)
        arising from a use of ‘theMessage’
      from the context: Message format m
        bound by the type signature for:
                   play :: forall format m.
                           Message format m =>
                           Synth format -> m -> IO ()
        at riddles/gadt-forget/closest-to-vivid.hs:36:1-54
      The type variable ‘format0’ is ambiguous
      Relevant bindings include
        msg :: m (bound at riddles/gadt-forget/closest-to-vivid.hs:37:19)
        play :: Synth format -> m -> IO ()
          (bound at riddles/gadt-forget/closest-to-vivid.hs:37:1)
      These potential instances exist:
        instance Message format (MessageA format)
          -- Defined at riddles/gadt-forget/closest-to-vivid.hs:30:10
        instance Message format (MessageB format)
          -- Defined at riddles/gadt-forget/closest-to-vivid.hs:32:10
    • In the second argument of ‘(++)’, namely ‘theMessage msg’
      In the second argument of ‘(++)’, namely
        ‘" now sounds like " ++ theMessage msg’
      In the second argument of ‘($)’, namely
        ‘name ++ " now sounds like " ++ theMessage msg’
   |
38 |   print $ name ++ " now sounds like " ++ theMessage msg

共有1个答案

贺玉石
2023-03-14

message是一个多参数类型类。为了确定使用哪个实例,需要对aformat进行具体选择。然而,该方法

theMessage :: a -> String

甚至没有提到格式,因此我们无法确定使用哪种具体类型来查找消息的实例。您可能得到的不明确类型错误就是关于这个(但这可能是一个棘手的错误消息,我并不责怪您仅仅启用了扩展)。

快速修复方法是使用ScopedTypeVariablesTypeApplications手动指定Format变量(或向Themessage添加Proxy Format参数)。

play :: forall format m. Message format m => Synth format -> m -> IO ()
play (Synth name) msg =
    print $ name ++ " now sounds like " ++ theMessage @format msg

但是,message类会引发错误使用类型类的危险信号。这并不总是糟糕的,但是每当您看到一个类的方法都具有类似于

:: a -> Foo
:: a -> Bar

即它们将单个a取反向位置,则可能根本不需要类型类。将类转换为数据类型通常更简洁,如下所示:

data Message format = Message { theMessage :: String }

其中每个方法都成为一个记录字段。然后,您实例化的具体类型,如messagea,将“降级”为函数:

messageA :: String -> Message format
messageA msg = Message { theMessage = msg }

无论何时,只要传递带有消息约束的a,只需传递消息即可。a化为乌有。

在你做了这个因素之后,你可能会注意到你写的很多东西都是同义反复的,而且是不必要的。很好!移开它!

 类似资料:
  • 为什么Java可以推断出多个上限类型的共同祖先,而不能推断出下限类型的共同祖先? 更具体地说,请考虑以下示例:

  • 问题内容: 我了解编译器使用目标类型来确定使通用方法调用适用的类型参数。例如,在以下语句中: 其中的签名中具有类型参数 在这种情况下,推断出的类型参数是。 现在考虑以下几点: 在这种情况下,推断的类型是什么?是吗 还是因为通配符告诉编译器任何类型都是可能的? 问题答案: 通配符的每种用法都有与之关联的不同类型。(通常,JLS将此称为“新鲜类型”。)例如,这就是这样的编译器错误的工作方式: 因为在这

  • 我知道编译器使用目标类型来确定使泛型方法调用适用的类型参数。例如,在以下声明中: 其中

  • 有没有办法告诉Java不要试图从使用基元类型的方法引用中推断类型? 这是我写的一个方法,原因现在无关紧要: 现在,如果您将方法引用传递给返回原始类型的“isEquals”,该怎么办? 这一切都很好,但Java也会接受这种奇怪的用法: 这是因为编译器将推断类型参数T为"

  • 问题内容: 它们有何不同?我有点困惑,因为它们似乎是相似的概念。 了解它们如何帮助优化编译时间? 问题答案: 从Swift自己的文档中: 类型安全 Swift是一种类型安全的语言。类型安全的语言鼓励您清楚代码可以使用的值的类型。 如果代码的一部分需要一个String,则不能错误地将其传递给Int。 类型推断 如果您 未 指定所需的值类型,则Swift会使用类型推断来得出适当的类型。通过类型推断,编

  • 问题内容: 我正在使用Java 的本地实现,该实现具有如下方法: 这两种方法可以编译并正常工作: 此方法无法编译: 错误: (我已经修剪了包限定符,以使错误更易读) 我可以通过指定类型进行编译: 但是为什么我需要呢?以及如何避免此代码混乱? 问题答案: 此代码应该起作用。 它在最新的JDK 1.8.0_121上编译。 无法在JDK 1.8.0-51上编译。 这意味着它在此版本的JDK中很可能是一个