为什么以下代码无法正确键入?
{-# LANGUAGE AllowAmbiguousTypes, MultiParamTypeClasses #-}
module Main where
class Interface a b c where
get :: a -> [b]
change :: b -> c
changeAll :: a -> [c]
changeAll = map change . get
main = return ()
如果我注释掉--changeAll = map change . get
的默认实例,一切似乎都很好。但是,在实例化到位后,出现此错误:
GHCi, version 8.6.5: http://www.haskell.org/ghc/ :? for help
[1 of 1] Compiling Main ( test.hs, interpreted )
test.hs:10:19: error:
• Could not deduce (Interface a0 b0 c)
arising from a use of ‘change’
from the context: Interface a b c
bound by the class declaration for ‘Interface’ at test.hs:5:7-15
The type variables ‘a0’, ‘b0’ are ambiguous
Relevant bindings include
changeAll :: a -> [c] (bound at test.hs:10:3)
• In the first argument of ‘map’, namely ‘change’
In the first argument of ‘(.)’, namely ‘map change’
In the expression: map change . get
|
10 | changeAll = map change . get
| ^^^^^^
test.hs:10:28: error:
• Could not deduce (Interface a b0 c0) arising from a use of ‘get’
from the context: Interface a b c
bound by the class declaration for ‘Interface’ at test.hs:5:7-15
The type variables ‘b0’, ‘c0’ are ambiguous
Relevant bindings include
changeAll :: a -> [c] (bound at test.hs:10:3)
• In the second argument of ‘(.)’, namely ‘get’
In the expression: map change . get
In an equation for ‘changeAll’: changeAll = map change . get
|
10 | changeAll = map change . get
| ^^^
我在这里错过明显的东西吗?
为了更好地说明问题,让我们将示例简化为一种方法:
class C a b c where
get :: a -> [b]
现在假设您有以下实例:
instance C Int String Bool where
get x = [show x]
instance C Int String Char where
get x = ["foo"]
然后想象您正在尝试调用该方法:
s :: [String]
s = get (42 :: Int)
根据s
的签名,编译器知道b ~ String
。通过get
的参数,编译器知道a ~ Int
。但是什么是c
?编译器不知道。无处可收集。
但是等等! C
的两个实例都匹配a ~ Int
和b ~ String
,那么该选择哪个呢?不清楚。信息不足。模棱两可。
这就是当您尝试在get
中调用change
和map change . get
时发生的事情:没有足够的类型信息供编译器理解a
,b
和c
用于get
呼叫或change
呼叫。哦,请记住:这两个调用可能来自不同的实例。没有什么可说的,它们必须与changeAll
本身来自同一实例。
首先,您可以使用功能依赖项,这是一种表示要确定c
的方法,即足以知道a
和b
:
class C a b c | a b -> c where ...
如果以这种方式声明该类,则编译器将拒绝具有相同a
和b
,但具有不同c
的多个实例,并且在另一方面,只需知道[ C0]和a
。
当然,您可以在同一个类上具有多个功能依赖项。例如,您可以声明知道任何两个变量就足以确定第三个变量:
b
请记住,对于您的class C a b c | a b -> c, a c -> b, b c -> a where ...
函数,这三个函数依赖项还不够,因为changeAll
的实现会“吞咽” changeAll
。也就是说,当它调用b
时,唯一已知的类型是get
。同样,当它调用a
时,唯一已知的类型是change
。这意味着,为了使c
的这种“吞咽”工作,必须单独由b
以及单独由a
确定:
c
当然,只有在程序逻辑确实具有某些变量由其他变量确定的属性的情况下,才有可能。如果您确实需要所有变量都独立,请继续阅读。
其次,您可以使用class Interface a b c | a -> b, c -> b where ...
:明确告诉编译器类型必须是什么
TypeApplications
不再有歧义。编译器确切知道要选择哪个实例,因为您已明确告诉它。
将此应用于您的 s :: String
s = get @Int @String @Bool 42 -- works
实现:
changeAll
((注:为了能够像这样在函数体中引用类型变量changeAll :: a -> [c]
changeAll = map (change @a @b @c) . get @a @b @c
,a
和b
,还需要启用c
)
当然,在调用ScopedTypeVariables
本身时,您也需要这样做,因为它的类型签名中也没有足够的信息:
changeAll