什么时候- xallowambiguousttypes是合适的?

我最近发布了一个关于语法- 2.0关于share定义的问题。我已经在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使用该签名进行编译。或者,我可以用

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

它是SyntacticN上的fundep所暗示的类型。这让我避免了扩展。当然这是

  • 在已经很大的签名中添加一个非常长的类型
  • 手动推导很累人
  • 由于资金原因,没有必要

我的问题是:

  1. 这是-XAllowAmbiguousTypes的可接受的用法吗?
  2. 一般来说,什么时候应该使用这个扩展?答案在这里表示“这几乎从来都不是一个好主意”。
  3. 虽然我读过的文档,但我仍然难以确定约束是否具有歧义。具体来说,考虑data . syntax . sugar中的这个函数:

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

    在我看来,fi(可能还有sup)在这里应该是模棱两可的,但它在编译时没有扩展名。为什么sugarSym是明确的,而share是?由于sharesugarSym的一个应用,所以share约束都直接来自sugarSym

6525 次浏览

我没有看到任何已发布的语法版本,其sugarSym的签名使用了这些确切的类型名称,因此我将使用提交8cfd02^的开发分支,这是仍然使用这些名称的最后一个版本。

那么,为什么GHC抱怨你的类型签名中的fi,而不是sugarSym?您所链接到的文档解释了,如果一个类型没有出现在约束的右边,那么它就是二义性的,除非约束使用函数依赖关系从其他非二义性类型推断出其他二义性类型。因此,让我们比较这两个函数的上下文,并寻找函数依赖性。

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,非歧义类型是subsigf,从这些类型中我们应该能够遵循函数依赖关系,以消除上下文中使用的所有其他类型,即supfi。实际上,SyntacticN中的f -> internal函数依赖使用我们的f来消除我们的fi的歧义,随后,sub1中的sub0函数依赖使用我们新消除的fi来消除sup(以及已经是非歧义的sig)。这就解释了为什么sugarSym不需要sub6扩展名。

现在让我们看看sugar。我注意到的第一件事是编译器抱怨一个模棱两可的类型,而是抱怨重叠的实例:

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认为你的类型有歧义,而是在检查你的类型是否有歧义时,GHC遇到了一个不同的、单独的问题。然后它告诉您,如果您告诉GHC不要执行歧义检查,它就不会遇到这个单独的问题。这解释了为什么启用allowambiguousttypes可以让你的代码编译。

但是,实例重叠的问题仍然存在。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])

你的类型签名使用SyntacticN (a -> (a -> b) -> b) fi,并且SyntacticN f fiSyntacticN (a -> f) (AST sym (Full ia) -> fi)都不是更好的选择。如果我将类型签名的这部分更改为SyntacticN a fiSyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi), GHC将不再抱怨重叠。

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

我发现AllowAmbiguousTypes非常方便与TypeApplications一起使用。考虑GHC。TypeLits中的函数natVal :: forall n proxy . KnownNat n => proxy n -> Integer

要使用这个函数,可以编写natVal (Proxy::Proxy5)。另一种样式是使用TypeApplications: natVal @5 ProxyProxy的类型是由类型应用程序推断出来的,每次调用natVal时都必须编写它,这很烦人。因此,我们可以启用AmbiguousTypes并写入:

{-# Language AllowAmbiguousTypes, ScopedTypeVariables, TypeApplications #-}


ambiguousNatVal :: forall n . (KnownNat n) => Integer
ambiguousNatVal = natVal @n Proxy


five = ambiguousNatVal @5 -- no `Proxy ` needed!

然而,请注意,一旦你变得模棱两可,你回不去了!