-XAllowAmbiguousTypes何时适合?
我最近发布了一个有关share
定义的关于语法-2.0的问题。 我已经在GHC 7.6中工作了:
{-# LANGUAGE GADTs, TypeOperators, FlexibleContexts #-} import Data.Syntactic import Data.Syntactic.Sugar.BindingT data Let a where Let :: Let (a :-> (a -> b) :-> Full b) share :: (Let :<: sup, sup ~ Domain b, sup ~ Domain a, Syntactic a, Syntactic b, Syntactic (a -> b), SyntacticN (a -> (a -> b) -> b) fi) => a -> (a -> b) -> b share = sugarSym Let
但是,GHC 7.8希望使用该签名来编译-XAllowAmbiguousTypes
。 或者,我可以用fi
replacefi
(ASTF sup (Internal a) -> AST sup ((Internal a) :-> Full (Internal b)) -> ASTF sup (Internal b))
这是SyntacticN上fundep所暗示的types。 这允许我避免扩展。 当然是这样
- 一个非常长的types添加到一个已经很大的签名
- 无聊的手动派生
- 由于fundep不必要
我的问题是:
- 这是一个可以接受的使用
-XAllowAmbiguousTypes
? - 一般来说,什么时候应该使用这个扩展? 这里的答案表明“这几乎不是一个好主意”。
-
尽pipe我已经阅读过文档 ,但仍然无法确定一个约束是否含糊不清。 具体来说,从Data.Syntactic.Sugar来考虑这个函数:
sugarSym :: (sub :<: AST sup, ApplySym sig fi sup, SyntacticN f fi) => sub sig -> f sugarSym = sugarN . appSym
在我看来,
fi
(也可能是sup
)在这里应该是模棱两可的,但是它没有扩展就编译。 为什么sugarSym
在share
是毫不含糊的? 由于share
是sugarSym
一个应用,share
限制都直接来自sugarSym
。
我没有看到任何已发布的语法的sugarSym
签名使用这些确切的types名称,所以我将使用开发分支在提交8cfd02 ^ ,最后一个版本仍然使用这些名称。
那么,为什么GHC会在你的型号签名中抱怨fi
,而不是sugarSym
呢? 你链接到的文档解释了,如果一个types没有出现在约束的右边,那么这个types是不明确的,除非约束使用函数依赖来从其他非歧义types中推导否则不明确的types。 所以我们来比较这两个函数的上下文并寻找函数依赖关系。
class ApplySym sig f sym | sig sym -> f, f -> sig sym class SyntacticN f internal | f -> internal sugarSym :: ( sub :<: AST sup , ApplySym sig fi sup , SyntacticN f fi ) => sub sig -> f share :: ( Let :<: sup , sup ~ Domain b , sup ~ Domain a , Syntactic a , Syntactic b , Syntactic (a -> b) , SyntacticN (a -> (a -> b) -> b) fi ) => a -> (a -> b) -> b
因此,对于sugarSym
,非模糊types是sub
, sig
和f
,并且从那些我们应该能够遵循函数依赖性来消除在上下文中使用的所有其他types,即sup
和fi
。 事实上, SyntacticN
的f -> internal
函数依赖使用我们的f
来消除我们的fi
,然后ApplySym
的f -> sig sym
函数依赖使用我们新消歧的fi
来消除sup
(和sig
,暧昧)。 所以这就解释了为什么sugarSym
不需要AllowAmbiguousTypes
扩展。
现在我们来看看sugar
。 我注意到的第一件事是,编译器不抱怨模糊的types,而是关于重叠的实例:
Overlapping instances for SyntacticN b fi arising from the ambiguity check for 'share' Matching givens (or their superclasses): (SyntacticN (a -> (a -> b) -> b) fi1) Matching instances: instance [overlap ok] (Syntactic f, Domain f ~ sym, fi ~ AST sym (Full (Internal f))) => SyntacticN f fi -- Defined in 'Data.Syntactic.Sugar' instance [overlap ok] (Syntactic a, Domain a ~ sym, ia ~ Internal a, SyntacticN f fi) => SyntacticN (a -> f) (AST sym (Full ia) -> fi) -- Defined in 'Data.Syntactic.Sugar' (The choice depends on the instantiation of 'b, fi') To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
所以,如果我正在阅读这个权利,并不是说GHC认为你的types是模棱两可的,而是在检查你的types是否含糊不清的时候,GHC遇到了另外一个单独的问题。 然后告诉你,如果你告诉GHC不要执行模糊检查,那么就不会遇到这个单独的问题。 这解释了为什么启用AllowAmbiguousTypes允许您的代码进行编译。
但是,重叠实例的问题仍然存在。 由GHC( SyntacticN f fi
和SyntacticN (a -> f) ...
)列出的两个实例确实相互重叠。 奇怪的是,似乎其中的第一个应该与任何其他可疑的实例重叠。 那么[overlap ok]
是什么意思?
我怀疑Syntactic是用OverlappingInstances编译的。 看代码 ,的确如此。
实验一下,看起来GHC在重叠的情况下是可以的,
{-# LANGUAGE FlexibleInstances, OverlappingInstances #-} class Foo a where whichOne :: a -> String instance Foo a where whichOne _ = "a" instance Foo [a] where whichOne _ = "[a]" -- | -- >>> main -- [a] main :: IO () main = putStrLn $ whichOne (undefined :: [Int])
但GHC不是重叠的情况下,当两者都不是明显比另一个好:
{-# LANGUAGE FlexibleInstances, OverlappingInstances #-} class Foo a where whichOne :: a -> String instance Foo (f Int) where -- this is the line which changed whichOne _ = "f Int" instance Foo [a] where whichOne _ = "[a]" -- | -- >>> main -- Error: Overlapping instances for Foo [Int] main :: IO () main = putStrLn $ whichOne (undefined :: [Int])
你的types签名使用了SyntacticN (a -> (a -> b) -> b) fi
,并且SyntacticN f fi
和SyntacticN (a -> f) (AST sym (Full ia) -> fi)
另一个。 如果我把你的types签名部分改成了SyntacticN a fi
或SyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi)
,GHC不再抱怨重叠。
如果我是你,我会查看这两个可能实例的定义,并确定这两个实现中的一个是否是你想要的。
我发现AllowAmbiguousTypes
与AllowAmbiguousTypes
一起使用非常方便。 考虑函数natVal :: forall n proxy . KnownNat n => proxy n -> Integer
natVal :: forall n proxy . KnownNat n => proxy n -> Integer
来自GHC.TypeLits的 natVal :: forall n proxy . KnownNat n => proxy n -> Integer
。
要使用这个函数,我可以写natVal (Proxy::Proxy5)
。 另一种方式是使用natVal @5 Proxy
: natVal @5 Proxy
。 Proxy
的types是由types应用程序推断的,每次调用natVal
时都需要编写它。 因此,我们可以启用AmbiguousTypes
并写入:
{-# Language AllowAmbiguousTypes, ScopedTypeVariables, TypeApplications #-} ambiguousNatVal :: forall n . (KnownNat n) => Integer ambiguousNatVal = natVal @n Proxy five = ambiguousNatVal @5 -- no `Proxy ` needed!
但是,请注意,一旦你模糊, 你不能回去 !