作者:LES--T单身 | 来源:互联网 | 2022-12-10 14:40
有时我会在签名中指定一些类型,例如,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
luqui..
6
Message
是一个多参数类型类.为了确定要使用的情况下,需要有一个具体的选择.a
而为format
.但是,方法
theMessage :: a -> String
甚至没有提到format
,所以我们无法确定使用哪种具体类型来查找实例Message
.你可能得到的模糊类型错误就是这个(但这可能是一个棘手的错误信息,我不会责怪你只是启用扩展).
快速解决方法是format
使用ScopedTypeVariables
和TypeApplications
(或添加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
然而,Message
该类引发了一个红旗,因为它误用了类型类.它并不总是坏的,但每当你看到一个类的方法都有类似的类
:: a -> Foo
:: a -> Bar
也就是说,它们a
在逆变位置中占据一席之地,很可能你根本不需要类型类.将类转换为数据类型通常更简洁,如下所示:
data Message format = Message { theMessage :: String }
其中每个方法成为记录字段.然后,您实例化的具体类型(例如您的MessageA
)将被"降级"为函数:
messageA :: String -> Message format
messageA msg = Message { theMessage = msg }
每当你将已通过的a
有Message
约束的,只是通过一个Message
代替. a
化为虚无.
在你进行这种因素分析之后,你可能会注意到你所写的很多内容都是同义反复和不必要的.好!去掉它!
1> luqui..:
Message
是一个多参数类型类.为了确定要使用的情况下,需要有一个具体的选择.a
而为format
.但是,方法
theMessage :: a -> String
甚至没有提到format
,所以我们无法确定使用哪种具体类型来查找实例Message
.你可能得到的模糊类型错误就是这个(但这可能是一个棘手的错误信息,我不会责怪你只是启用扩展).
快速解决方法是format
使用ScopedTypeVariables
和TypeApplications
(或添加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
然而,Message
该类引发了一个红旗,因为它误用了类型类.它并不总是坏的,但每当你看到一个类的方法都有类似的类
:: a -> Foo
:: a -> Bar
也就是说,它们a
在逆变位置中占据一席之地,很可能你根本不需要类型类.将类转换为数据类型通常更简洁,如下所示:
data Message format = Message { theMessage :: String }
其中每个方法成为记录字段.然后,您实例化的具体类型(例如您的MessageA
)将被"降级"为函数:
messageA :: String -> Message format
messageA msg = Message { theMessage = msg }
每当你将已通过的a
有Message
约束的,只是通过一个Message
代替. a
化为虚无.
在你进行这种因素分析之后,你可能会注意到你所写的很多内容都是同义反复和不必要的.好!去掉它!