因此,假设您有两个这样定义的类型类:
{-# LANGUAGE MultiParamTypeClasses #-}
class F a c where f :: a -> c
class G c b where g :: c -> b
然后,您想使用f和g以一般方式定义新函数h。
h a = g (f a)
我们知道此函数具有类型,a -> b
因此c在其中隐含。我想把它留给的实现者f
以及可能是g
什么c
。Haskell抱怨这句话c
模棱两可。
然后按照错误提示,我打开了这个扩展名:
{-# LANGUAGE AllowAmbiguousTypes #-}
现在可以了!真好
我相信通常作为一种好的软件工程实践,我想写出我的函数的明确规范,以告诉编译器我期望我的函数应表现为什么样。这样以后的编译器就可以抱怨我不尊重我的设置。
因此,我想在其之前添加函数的类型:
h :: (F a c, G c b) => a -> b
h a = g (f a)
现在,类型歧义错误再次出现...为什么?
总结一下为什么Haskell在下面的这段代码中会抱怨呢?即使显式启用了AllowAmbiguousTypes。如何在保留显式函数类型定义的同时进行修复?我知道删除该函数的类型定义可以解决该问题,但是我不想对事物进行低估。
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
class F a c where f :: a -> c
class G c b where g :: c -> b
h :: (F a c, G c b) => a -> b
h a = g (f a)
Haskell为什么不抱怨呢?
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
class F a c where f :: a -> c
class G c b where g :: c -> b
h a = g (f a)
错误讯息:
error:
* Could not deduce (G c0 b) arising from a use of `g'
from the context: (F a c, G c b)
bound by the type signature for:
h :: forall a c b. (F a c, G c b) => a -> b
The type variable `c0' is ambiguous
Relevant bindings include
h :: a -> b
* In the expression: g (f a)
In an equation for `h': h a = g (f a)
|
| h a = g (f a)
| ^^^^^^^
error:
* Could not deduce (F a c0) arising from a use of `f'
from the context: (F a c, G c b)
bound by the type signature for:
h :: forall a c b. (F a c, G c b) => a -> b
The type variable `c0' is ambiguous
Relevant bindings include
a :: a
h :: a -> b
* In the first argument of `g', namely `(f a)'
In the expression: g (f a)
In an equation for `h': h a = g (f a)
|
| h a = g (f a)
| ^^^
您的代码含糊不清,以至于编译器无法自动解决。假设:
class F a c where f :: a -> c
class G c b where g :: c -> b
instance F Int String where f = show
instance G String Bool where g = null
h :: (F Int c, G c Bool) => Int -> Bool
h a = g (f a)
现在,最后一行使用了哪些实例?我们有两个选择:使用上下文提供的实例(F Int c, G c Bool)
,或者忽略该上下文,并使用上述实例String
作为中间类型。两种解释都是正确的,实际上我们可以明确地写
h1 :: forall c. (F Int c, G c Bool) => Int -> Bool
h1 a = (g :: c -> Bool) (f a)
h2 :: forall c. (F Int c, G c Bool) => Int -> Bool
h2 a = (g :: String -> Bool) (f a)
选择一种或另一种方式。GHC无法以合理的方式为我们做出选择。它可以根据一些启发式方法选择一个,但是这可能会给程序员带来很多惊喜。因此,我们可以争辩说,GHC绝对不能选择,不要报告歧义,而应该让程序员阐明其意图。
最后,请注意,您的代码不包含上述两个实例这一事实是不相关的,因为可以在以后甚至在另一个模块中添加它们,因此GHC必须保持保守,并避免假定它们将永远不存在。
Haskell为什么不抱怨呢?
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE AllowAmbiguousTypes #-} class F a c where f :: a -> c class G c b where g :: c -> b h x = g (f x) -- [renamed to x for clarity]
好点子。在这里,GHC可以找到最通用的类型,即
h :: forall a b c. (F a c, G c b) => a -> b
h x = (g :: c -> b) ((f :: a -> b) x)
由于这里是GHC添加了类型变量c
,因此GHC可以确保该类型为中间类型。毕竟,在类型推断期间创建了该类型变量以表示中间类型。
而是,当用户显式编写上下文时,GHC无法猜测用户的意图。即使实际上不太可能,用户也可能不想使用该实例,而是想要使用另一个实例(在程序中可用,而在上下文中不存在)。
考虑这种情况也可能会有所帮助:
data T = ....
h :: forall a b c. (F a c, G c b, F a T, G T b) => a -> b
h x = g (f x)
我认为您可以同意应该拒绝此代码:中间类型可以是T
或c
,并且没有理智的方法来解决它。现在考虑这种情况:
h :: forall a b c. (F a c, G c b) => a -> b
h x = g (f x)
instance F a T where ...
instance G T b where ...
现在,这与之前的情况没有太大不同。我们没有在上下文中有两个选项,而是将一个选项移到了外面。但是,GHC仍然有两个选择。因此,同样,理智的事情是拒绝代码,并要求程序员提供更多细节。
在GHCi中,一个更简单的方案是:
> :set -XScopedTypeVariables
> :set -XAllowAmbiguousTypes
> class C a where c :: String
> instance C Int where c = "Int"
> instance C Bool where c = "Bool"
> let g :: forall a. C a => String ; g = c
<interactive>:7:40: error:
* Could not deduce (C a0) arising from a use of `c'
在这里,GHC怎么会知道,当我写的时候,g = c
我的意思是“c
来自上下文的来临C a
?”我可以写出“c
来自实例的for Int
”的意思Bool
。
GHC在内部生成一个新的类型变量a0
,然后尝试解决约束C a0
。它有三种选择:选择a0 = a
,a0 = Int
或a0 = Bool
。(以后可以添加更多实例!)
因此,它是模棱两可的,并且没有理智的方法可以在不猜测程序员意图的情况下对其进行修复。唯一安全的选择是拒绝。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句