-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 。 或者,我可以用fireplacefi

 (ASTF sup (Internal a) -> AST sup ((Internal a) :-> Full (Internal b)) -> ASTF sup (Internal b)) 

这是SyntacticN上fundep所暗示的types。 这允许我避免扩展。 当然是这样

  • 一个非常长的types添加到一个已经很大的签名
  • 无聊的手动派生
  • 由于fundep不必要

我的问题是:

  1. 这是一个可以接受的使用-XAllowAmbiguousTypes
  2. 一般来说,什么时候应该使用这个扩展? 这里的答案表明“这几乎不是一个好主意”。
  3. 尽pipe我已经阅读过文档 ,但仍然无法确定一个约束是否含糊不清。 具体来说,从Data.Syntactic.Sugar来考虑这个函数:

     sugarSym :: (sub :<: AST sup, ApplySym sig fi sup, SyntacticN f fi) => sub sig -> f sugarSym = sugarN . appSym 

    在我看来, fi (也可能是sup )在这里应该是模棱两可的,但是它没有扩展就编译。 为什么sugarSymshare是毫不含糊的? 由于sharesugarSym一个应用, 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是subsigf ,并且从那些我们应该能够遵循函数依赖性来消除在上下文中使用的所有其他types,即supfi 。 事实上, SyntacticNf -> internal函数依赖使用我们的f来消除我们的fi ,然后ApplySymf -> 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 fiSyntacticN (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 fiSyntacticN (a -> f) (AST sym (Full ia) -> fi)另一个。 如果我把你的types签名部分改成了SyntacticN a fiSyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi) ,GHC不再抱怨重叠。

如果我是你,我会查看这两个可能实例的定义,并确定这两个实现中的一个是否是你想要的。

我发现AllowAmbiguousTypesAllowAmbiguousTypes一起使用非常方便。 考虑函数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 ProxynatVal @5 ProxyProxy的types是由types应用程序推断的,每次调用natVal时都需要编写它。 因此,我们可以启用AmbiguousTypes并写入:

 {-# Language AllowAmbiguousTypes, ScopedTypeVariables, TypeApplications #-} ambiguousNatVal :: forall n . (KnownNat n) => Integer ambiguousNatVal = natVal @n Proxy five = ambiguousNatVal @5 -- no `Proxy ` needed! 

但是,请注意,一旦你模糊, 你不能回去 !