什么时候-XAllowAmbiguousTypes合适?

时间:2014-05-15 17:27:09

标签: haskell ghc

我最近就share的定义发布了关于questionsyntactic-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使用该签名进行编译。或者,我可以用{/ 1>替换fi

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

这是SyntacticN上的fundep隐含的类型。这允许我避免扩展。当然这是

  • 要添加到已经很大的签名
  • 的非常长的类型
  • 无法手动派生
  • 由于fundep而不必要

我的问题是:

  1. 这是-XAllowAmbiguousTypes的可接受用途吗?
  2. 一般情况下,何时应使用此扩展程序?答案here表明“这几乎不是一个好主意”。
  3. 虽然我读过the docs,但我仍然无法确定约束是否含糊不清。具体来说,请考虑Data.Syntactic.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

2 个答案:

答案 0 :(得分:12)

我没有看到任何已发布的syntactic版本,sugarSym的签名使用了这些确切的类型名称,因此我将使用the development branch at commit 8cfd02^,这是仍使用这些名称的最后一个版本。

那么,为什么GHC会抱怨你的类型签名中的fi而不是sugarSym的{​​{1}}?您链接到的文档解释了如果类型不出现在约束的右侧,则类型是不明确的,除非约束使用函数依赖性从其他非模糊类型推断其他模糊类型。因此,让我们比较两个函数的上下文,并寻找函数依赖。

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编译了Syntactic。看着the code,确实如此。

尝试一下,似乎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) fiSyntacticN f fiSyntacticN (a -> f) (AST sym (Full ia) -> fi)都不比另一个更合适。如果我将类型签名的这一部分更改为SyntacticN a fiSyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi),则GHC不再抱怨重叠。

如果我是你,我会查看the definition of those two possible instances并确定这两个实现中的一个是否是你想要的那个。

答案 1 :(得分:2)

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

要使用此功能,我可以写natVal (Proxy::Proxy5)。另一种风格是使用TypeApplicationsnatVal @5 ProxyProxy的类型是由类型应用程序推断的,每次调用natVal时都必须编写它是很烦人的。因此,我们可以启用AmbiguousTypes并写入:

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

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

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

但请注意,一旦你不明确,you can't go back