在 Haskell 中,即使启用了 AllowAmbiguousTypes,为什么类型仍然不明确?
In Haskell, why are types ambiguous even with AllowAmbiguousTypes enabled?
所以假设您有两个类型 类 定义如下:
{-# 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 必须保守并避免假设它们永远不会存在。
And why doesn't Haskell complain about this?
{-# 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
的意思是 "the c
coming from the context C a
? I could have written that meaning " 来自 Int
" 实例的 c
或 Bool
.
GHC 在内部生成一个新类型变量 a0
,然后尝试求解约束 C a0
。它有三个选择:选择 a0 = a
、a0 = Int
或 a0 = Bool
。 (以后可以添加更多实例!)
所以,它是模棱两可的,如果不猜测程序员的意图,就没有理智的方法来修复它。唯一安全的选择是拒绝。
所以假设您有两个类型 类 定义如下:
{-# 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 必须保守并避免假设它们永远不会存在。
And why doesn't Haskell complain about this?
{-# 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
的意思是 "the c
coming from the context C a
? I could have written that meaning " 来自 Int
" 实例的 c
或 Bool
.
GHC 在内部生成一个新类型变量 a0
,然后尝试求解约束 C a0
。它有三个选择:选择 a0 = a
、a0 = Int
或 a0 = Bool
。 (以后可以添加更多实例!)
所以,它是模棱两可的,如果不猜测程序员的意图,就没有理智的方法来修复它。唯一安全的选择是拒绝。