-XAllowAmbiguousTypes什么时候合适?


212

我最近发布了一个问题,关于句法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使用该签名进行编译。或者,我可以代替fi

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

这是基金暗示的类型SyntacticN。这使我避免扩展。当然是

  • 一个很长的类型以添加​​到已经很大的签名中
  • 难以手动导出
  • 由于资金不足而不必要

我的问题是:

  1. 这是可接受的用法-XAllowAmbiguousTypes吗?
  2. 通常,何时应使用此扩展名?这里的答案表明“这几乎从来不是一个好主意”。
  3. 尽管我已经阅读了文档,但是在确定约束是否模棱两可时仍然遇到困难。具体来说,请考虑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是的应用sugarSymshare约束全部来自sugarSym


4
有什么理由不能只为sugarSym Let,而(SyntacticN f (ASTF sup a -> ASTF sup (a -> b) -> ASTF sup b), Let :<: sup) => f又不涉及模棱两可的类型变量使用推断类型?
kosmikus 2014年

3
@kosmikus Sorrt花费了很长时间才回复。此代码不能与推断的签名编译share,但被使用,也可以在问题中提到的签名时编译。您在上一篇文章
克罗基亚2014年

3
未定义的行为可能不是最合适的术语。仅仅基于一个程序就很难理解。问题是可抽取性,GHCI无法证明程序中的类型。有一个很长的讨论可能会让您对此主题感兴趣。haskell.org/pipermail/haskell-cafe/2008-April/041397.html
BlamKiwi 2014年

6
至于(3),由于SyntacticN(即f-»fi)和ApplySym(特别是fi-> sig,sup)定义中的功能依赖关系,该类型不是模棱两可的。从这一点,你得到的f就足以完全消除歧义sigfisup
user2141650

3
@ user2141650很抱歉,回复花了很长时间。你是说在fundep SyntacticN品牌fi在明确的sugarSym,但后来为什么同样不是真实fishare
crockeea 2015年

Answers:


12

我没有看到任何发布的语法签名的版本sugarSym使用那些确切的类型名称,因此我将在commit 8cfd02 ^(最后一个仍使用这些名称的版本)使用development分支

那么,为什么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。而事实上,f -> internal在函数依赖SyntacticN使用我们f的歧义我们fi,此后f -> sig sym在函数依赖ApplySym使用我们新义性fi消除歧义sup(并且sig,这已经是无歧义)。因此,这说明了为什么sugarSym不需要AllowAmbiguousTypes扩展。

现在让我们看一下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不执行歧义检查,则它不会遇到该单独的问题。这解释了为什么启用AllowAmbiguousTypes可以编译您的代码。

但是,重叠实例的问题仍然存在。GHC(SyntacticN f fiSyntacticN (a -> f) ...)列出的两个实例确实相互重叠。奇怪的是,其中第一个似乎应该与任何其他实例重叠,这是可疑的。那是什么[overlap ok]意思呢?

我怀疑语法是使用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 fi都不SyntacticN (a -> f) (AST sym (Full ia) -> fi)比其他任何一种更合适。如果我将您的类型签名的那一部分更改为SyntacticN a fiSyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi),GHC将不再抱怨重叠。

如果您是我,我将查看这两个可能实例的定义,并确定这两个实现之一是否是您想要的一种。


2

我发现AllowAmbiguousTypes可以很方便地与结合使用TypeApplications。考虑功能natVal :: forall n proxy . KnownNat n => proxy n -> IntegerGHC.TypeLits

要使用此功能,我可以编写natVal (Proxy::Proxy5)。另一种样式是使用TypeApplicationsnatVal @5 Proxy。该类型Proxy是由应用程序类型推断,这很烦人在每次通话时间把它写natVal。因此,我们可以启用AmbiguousTypes并编写:

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

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

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

但是,请注意,一旦变得模棱两可,就无法返回

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.