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

Jef*_*own 2 haskell types

有时我会在签名中指定一些类型,例如,aGHC会回应它无法推断出它的类型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
Run Code Online (Sandbox Code Playgroud)

这会产生以下错误.

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
Run Code Online (Sandbox Code Playgroud)

luq*_*qui 6

Message是一个多参数类型类.为了确定要使用的情况下,需要有一个具体的选择.a format.但是,方法

theMessage :: a -> String
Run Code Online (Sandbox Code Playgroud)

甚至没有提到format,所以我们无法确定使用哪种具体类型来查找实例Message.你可能得到的模糊类型错误就是这个(但这可能是一个棘手的错误信息,我不会责怪你只是启用扩展).

快速解决方法是format使用ScopedTypeVariablesTypeApplications(或添加Proxy 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
Run Code Online (Sandbox Code Playgroud)

然而,Message该类引发了一个红旗,因为它误用了类型类.它并不总是坏的,但每当你看到一个类的方法都有类似的类

:: a -> Foo
:: a -> Bar
Run Code Online (Sandbox Code Playgroud)

也就是说,它们a在逆变位置中占据一席之地,很可能你根本不需要类型类.将类转换为数据类型通常更简洁,如下所示:

data Message format = Message { theMessage :: String }
Run Code Online (Sandbox Code Playgroud)

其中每个方法成为记录字段.然后,您实例化的具体类型(例如您的MessageA)将被"降级"为函数:

messageA :: String -> Message format
messageA msg = Message { theMessage = msg }
Run Code Online (Sandbox Code Playgroud)

每当你将已通过的aMessage约束的,只是通过一个Message代替. a化为虚无.

在你进行这种因素分析之后,你可能会注意到你所写的很多内容都是同义反复和不必要的.好!去掉它!