haskell - 当我指定 x 的类型为 a 时,为什么 Haskell 会尝试推断它的类型为 a0 ?

标签 haskell types

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

这是一个简短的示例。 (要查看此代码(包括解释其尝试执行的操作的注释),请参阅 here 。)

{-# 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

最佳答案

Message 是一个多参数类型类。为了确定使用哪个实例,需要对a格式进行具体选择。不过,方法

theMessage :: a -> String

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

快速解决方法是使用 ScopedTypeVariablesTypeApplications 手动指定 format 变量(或添加代理格式) > theMessage 的参数)。

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 }

每当您传递带有 Message 约束的 a 时,只需传递 Message 即可。 a 化为虚无。

进行因式分解后,您可能会注意到您所写的很多内容都是同义反复且不必要的。好的!去掉它!

关于haskell - 当我指定 x 的类型为 a 时,为什么 Haskell 会尝试推断它的类型为 a0 ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51370183/

相关文章:

haskell - 如何阻止随机性渗透到 Haskell 中的代码中?

ruby - 确定ruby中对象的类型

c# - 为什么 c# 中没有 "is not"关键字?

Haskell "not"类型约束

java - 类型转换为父类(super class)并调用重写方法是否可以接受? ( java )

c# - 将 Null 值分配给 DataTable 中的整数列

haskell - 在 Haskell 中键入删除?

haskell - Haskell 需要垃圾收集器吗?

haskell - 如何阅读haskell中的语法 `Typ{..}`?

haskell -斯科蒂 : Set custom headers (x-frame-options)