什么时候GHC可以推断约束变量?

时间:2014-05-28 17:35:23

标签: haskell ghc constraint-kinds

我收到类型推断错误,因为GHC不会推断出约束变量。它看起来可以通过一阶统一来推断。在进一步调查中,我发现插入let-bindings会改变类型推断的行为。我想知道GHC在做什么。

此处的代码演示了此问题。 newtype ConstrainedF c代表一个多态函数,其类型参数受c约束。据我所知,GHC根据c给出的值不会推断ConstrainedF

{-# LANGUAGE RankNTypes, ScopedTypeVariables, ConstraintKinds, MonoLocalBinds #-}

import Data.Monoid
import GHC.Prim(Constraint)

newtype ConstrainedF c =
  ConstrainedF { runConstrainedF :: forall a. c a => [a] -> a}

applyConstrainedF :: forall c a. c a => ConstrainedF c -> [a] -> a
applyConstrainedF f xs = runConstrainedF f xs

-- GHC cannot infer the type parameter of ConstrainedF
foo :: [Int]
foo = applyConstrainedF (ConstrainedF mconcat) [[1], [2]]
--foo = applyConstrainedF (ConstrainedF mconcat :: ConstrainedF Monoid) [[1], [2]]

应该可以推断应用程序ConstrainedF mconcat中的类型:

  1. ConstrainedF的类型为forall c. (forall a. c a => [a] -> a) -> ConstrainedF c
  2. mconcat的类型为forall b. Monoid b => [b] -> b
  3. forall b. Monoid b => [b] -> b通过作业forall a. c a => [a] -> aa := bc := Monoid统一。
  4. 然而,GHC抱怨:

    Could not deduce (Monoid a) arising from a use of `mconcat'
    from the context (c0 a).
    

    关于约束变量,我必须遵循哪些规则才能使GHC推断类型?


    模糊类型错误的典型解决方案是添加代理值以约束模糊类型。当我尝试它时,这很挑剔。如果我只是添加一个额外的参数来约束c的类型,它可以工作:

    data Cst1 (c :: * -> Constraint) = Cst1
    
    monoid :: Cst1 Monoid
    monoid = Cst1
    
    applyConstrainedF :: forall c a. c a => ConstrainedF c -> Cst1 c -> [a] -> a
    applyConstrainedF f _ xs = runConstrainedF f xs
    
    foo :: [Int]
    foo = applyConstrainedF (ConstrainedF mconcat) monoid [[1], [2]]
    

    但在foo中引入let绑定会混淆类型推断,并且无法再将cMonoid统一起来。

    foo_doesn't_work :: [Int]
    foo_doesn't_work = let cf = ConstrainedF mconcat
                       in applyConstrainedF cf monoid [[1], [2]]
    

    由于类型推断在这两个函数之一中得到正确的答案,这告诉我GHC将在某些情况下统一约束变量而不是其他情况。我不明白发生了什么。

1 个答案:

答案 0 :(得分:2)

这里的问题是子类型。在您的示例中,c也可以是(Monoid b, Eq b)

此外,您可以使用Data.Typeable来检查c实例化的内容。

或者,如果您要求(c, d)(一对约束)与Monoid“统一”怎么办?


你问题第二部分的答案是 - 你猜对了! - 让我们进行概括。

我知道你已经猜到了,因为你添加了MonoLocalBinds pragma。但是,它并没有达到您的期望。你看,它只会停止真正本地绑定的泛化 - 依赖于函数参数或其他本地绑定的绑定。

E.g。这有效:

foo_does_work :: () -> [Int]
foo_does_work x =
  let cf = const (ConstrainedF mconcat) x
  in applyConstrainedF cf monoid [[1], [2]]

有关详细信息,请参阅Let generalisation: Which bindings are affected?