在Haskell中,为什么即使启用了AllowAmbigUstypes,类型也不明确?
假设有两个类型类定义如下:在Haskell中,为什么即使启用了AllowAmbigUstypes,类型也不明确?,haskell,typeclass,Haskell,Typeclass,假设有两个类型类定义如下: {-#语言多段类型类} 类别F a c,其中F::a->c 类别G c b,其中G::c->b 然后你想用f和g,用一般的方法定义一个新的函数h ha=g(fa) 我们知道这个函数的类型是a->b,所以c是隐式的。我想把它留给f和g的实现者c可能是什么。哈斯克尔抱怨说,c这句话模棱两可 然后,根据错误提示,我打开了此扩展: {-#语言允许使用模糊类型} 现在它工作了!很好 我相信,通常作为一种良好的软件工程实践,我希望编写函数的显式规范,告诉编译器我期望函数的行
{-#语言多段类型类}
类别F a c,其中F::a->c
类别G c b,其中G::c->b
然后你想用f和g,用一般的方法定义一个新的函数h
ha=g(fa)
我们知道这个函数的类型是a->b
,所以c是隐式的。我想把它留给f
和g
的实现者c
可能是什么。哈斯克尔抱怨说,c
这句话模棱两可
然后,根据错误提示,我打开了此扩展:
{-#语言允许使用模糊类型}
现在它工作了!很好
我相信,通常作为一种良好的软件工程实践,我希望编写函数的显式规范,告诉编译器我期望函数的行为。这样以后的编译器可以抱怨我不尊重我设置的内容
因此,我想在前面添加函数的类型:
h::(fac,gcb)=>a->b
h a=g(f a)
现在类型模糊性错误再次出现。。。为什么?
总结一下为什么Haskell会抱怨下面这段代码?即使AllowAmbigUstypes已明确启用。如何在保留显式函数类型定义的同时修复它?我知道删除函数的类型定义可以解决这个问题,但我不想把事情说得太具体
{-#语言多段类型类}
{-#语言允许歧义类型}
类别F a c,其中F::a->c
类别G c b,其中G::c->b
h::(fac,gcb)=>a->b
h a=g(f a)
为什么哈斯克尔不抱怨这一点
{-# 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]
{-#语言多段类型类}
{-#语言允许歧义类型}
类别F a c,其中F::a->c
类别G c b,其中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)
提供的实例,或者忽略该上下文并使用上面的实例,将字符串
作为中间类型。这两种解释都是正确的,事实上,我们可以明确地写出来
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必须保守,避免假设它们永远不存在
哈斯克尔为什么不抱怨呢
{-# 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'
:set-XScopedTypeVariables
>:set-XAllowAmbiguousTypes
>类C,其中C::String
>实例C Int,其中C=“Int”
>实例C Bool,其中C=“Bool”
>让g::对于所有a。cA=>字符串;g=c
:7:40:错误:
*无法推断因使用“C”而产生的(cA0)
在这里,GHC怎么知道当我写g=c
时,我的意思是“来自上下文ca
的c
?我本可以写这句话,意思是“来自Int
实例的c
”。或者Bool
GHC在内部生成一个新的类型变量a0
,然后尝试解决约束ca0
。它有三个选择:选择a0=a
,a0=Int
,或a0=Bool
(以后可以添加更多实例!)
因此,它是模棱两可的,在不猜测程序员意图的情况下,没有合理的方法来修复它。唯一安全的选择是拒绝。因为不能根据类型签名确定
c
应该是什么。因此“用户”“的函数,不能决定c
@WillemVanOnsem,但如果不指定h::(fac,gcb)=>a->b,则该函数有效。”。那么,为什么它在一种情况下有效而在另一种情况下无效呢?我不是这些方面的专家,但我相信问题是因为GHC不知道您要使用哪个c
,启用函数依赖关系可能会有所帮助。如果您启用-XScopedTypeVariables
,并将签名更改为h::for all c b a。(F a c,G c b)=>a->b
(能够在实现中引用中间类型c
),并启用-XTypeApplications
,然后您可以编写ha=G@c(F a)
。我不确定是否理解您的答案。我也在学习哈斯克尔。为什么会是d