推断的泛型函数typechecks作为返回类型,而不是参数类型

时间:2019-05-26 16:00:15

标签: haskell generic-programming monomorphism-restriction

我正在学习SYB和n级类型,并遇到了一个令人困惑的情况,即单态限制。

我编写了一个函数来查找与谓词匹配的最浅条目。我想使用Alternative来接受类似于谓词的函数,而不是归约函数,然后自己将其转换为通用函数。我决定在let块中省略类型注释,以了解单态减少将如何影响此实现中的类型:

shallowest :: (Alternative f, Typeable b) => (b -> f a) -> GenericQ (f a)
shallowest p z =
  let op = (empty `mkQ` p) in
    op z <|> foldl (<|>) empty (gmapQ op z)

这会产生一个错误,表明let绑定中的歧义会阻止类型检查器解决约束Data a1

Error: • Couldn't match type ‘d’ with ‘a1’
  ‘d’ is a rigid type variable bound by
    a type expected by the context:
      forall d. Data d => d -> m a
  ‘a1’ is a rigid type variable bound by
    the type signature for:
      shallowest :: (b -> m a) -> GenericQ (m a)

(其他类似head (gmapQ op z)的主体会引起“ let绑定的含糊不清的明确错误,这沿“由于使用'mkQ'而无法推断(可键入a0)”的思路进行;我也还没有弄清楚为什么上面的表格不行。

当我们在let的{​​{1}}块中添加注释时(需要ScopedTypeVariables),类型错误就会消失。

但是,我感到困惑的是,似乎可以推断出op :: GenericQ (f a)上的Data约束 :跟随类型在返回类型时进行检查:

op

有什么区别?两种情况都要求shallowest p = let { op = (empty `mkQ` p) } in op op;我看到的唯一区别是,第一个位于参数位置,第二个位于返回位置。

2 个答案:

答案 0 :(得分:2)

在第二个摘录中,op实际上不是多态的。

shallowest p = let { op = (empty `mkQ` p) } in op

这是一个微妙的区别:op实际上是单态的,但是在开放的上下文中。使用通常的键入判断符号,op右侧的in的键入如下所示:

 types         values
 ↓             ↓
 x, a, f, ...; op :: x -> f a, ... |- op :: x -> f a
                                            ↑
                                            monotype (no "forall")

 In English: "op has type (x -> f a) in the context consisting of type variables (x, a, f, ...) and values (op :: x -> f a, ...)"

shallowest通过在顶层进行的 generalization 步骤变为多态。如果在具有类型变量x, a, f, ...的上下文中,shallowest的主体具有类型x -> f a,那么我们可以“关闭上下文”并将类型变量移到{{1 }}。类型派生看起来像这样:

shallowest :: forall x a f. x -> f a

(类型类和统一算法使事情变得更加复杂,但这超出了答案的范围。)

使用多态进行类型检查的主要问题是确定何时应该进行泛化。由于缺少主体类型和不确定性,因此没有通用的解决方案。因此,类型检查器的实现必须做出一些选择。

在Haskell中,泛化发生在以下位置(列表可能并不详尽),这是很自然的选择:

  • 函数定义,即 x, a, f |- (let op = ... in op) :: x -> f a ⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻ (generalization) |- (let op = .... in op) :: forall x a f. x -> f a 和具有至少一个显式参数的顶级绑定(这里是单态限制);

  • 高级函数的多态参数:如果您有函数let,则在进行f :: (forall a. w a) -> r的类型检查时,f x将概括a

  • ,当然,当由显式注释x指示时。

答案 1 :(得分:0)

初步说明:鉴于此处提供的证据,我认为您正在使用:

  • type GenericQ r = forall a . Data a => a -> r from syb
  • gmapQ :: Data a => (forall d. Data d => d -> u) -> a -> [u] from Data.Data

如果我误会了,请告诉我。另外,后面的任何forall都将明确写入。


在这里,不仅有吸引人的目光。 As Li-yao Xia suggests,这是涉及op类型的一般化问题。关于您对shallowest的第一个定义,有三个相关事实:

  1. 在归纳之前,op的推断类型为Data d => d -> f a。给定Data d约束,单态约束的规则1(请参阅subsection 4.5.5 of the Report)意味着这种类型的d无法推广。

  2. shallowest的正文中,op出现在两个位置。第一个是op z,其中z :: a1shallowest签名的约束和约束。结果是op的出现并不需要对参数类型进行泛化:就其而言,op的类型可以为forall f a. a1 -> f a ,在类型变量a1中是单态的(我从subsection 4.5.4 of the Report获得了此术语)。

  3. 不过,其他情况是gmapQ op zgmapQ具有等级2类型,需要多态参数。既然如此,这种情况需要概括op的参数类型,如夏丽瑶回答的结尾所述。

#1和#3是矛盾的要求,因此会出现类型错误,可以通过禁用单态性限制或要求op对带有签名的参数类型为多态来避免。由于#2中描述了op的另一次出现,该情况被报告为涉及这两次出现的不匹配。


下面是一个更简单的扩展示例,它可能有助于了解正在发生的情况。 (如果要将以下代码段放入GHCi中,除了-XRankNTypes之外,还应该设置-XMonomorphismRestriction-XNoExtendedDefaultRules才能看到相同的结果。)

这是一个具有等级2类型的函数,它将扮演gmapQ的角色:

glub :: (forall x. Show x => x -> String) -> String
glub f = f 7

现在让我们尝试一种类似于shallowest ...

的情况。
foo1 :: forall a. Show a => a -> String
foo1 x = bar x ++ glub bar
  where
  bar = show

...并且有您的错误:

<interactive>:506:23: error:
    • Couldn't match type ‘x’ with ‘a’
      ‘x’ is a rigid type variable bound by
        a type expected by the context:
          forall x. Show x => x -> String
        at <interactive>:506:18-25
      ‘a’ is a rigid type variable bound by
        the type signature for:
          foo1 :: forall a. Show a => a -> String
        at <interactive>:505:1-38
      Expected type: x -> String
        Actual type: a -> String
    • In the first argument of ‘glub’, namely ‘bar’
      In the second argument of ‘(++)’, namely ‘glub bar’
      In the expression: bar x ++ glub bar
    • Relevant bindings include
        bar :: a -> String (bound at <interactive>:508:3)
        x :: a (bound at <interactive>:506:5)
        foo1 :: a -> String (bound at <interactive>:506:1)

bar的签名应添加通配符的地方添加一个附加错误,该错误提示性更高:

foo2 :: forall a. Show a => a -> String
foo2 x = bar x ++ glub bar
  where
  bar :: _
  bar = show
• Found type wildcard ‘_’ standing for ‘a -> String’
  Where: ‘a’ is a rigid type variable bound by
           the type signature for:
             foo2 :: forall a. Show a => a -> String
           at <interactive>:511:1-38
  To use the inferred type, enable PartialTypeSignatures
• In the type signature: bar :: _
  In an equation for ‘foo2’:
      foo2 x
        = bar x ++ glub bar
        where
            bar :: _
            bar = show
• Relevant bindings include
    x :: a (bound at <interactive>:512:5)
    foo2 :: a -> String (bound at <interactive>:512:1)

请注意,如何将通配符“代表a -> String声明为与afoo2的类型签名约束的独立事实。我相信这对应于我在上面的第2点中提到的类型变量和多态之间的单态区别。

赋予bar多态类型签名使其可以工作:

foo3 :: forall a. Show a => a -> String
foo3 x = bar x ++ glub bar
  where
  bar :: forall b. Show b => b -> String
  bar = show

使bar的定义也很有意义,通过将其设为a "function binding" rather than a "simple pattern binding"来逃避单态性限制:

foo4 :: forall a. Show a => a -> String
foo4 x = bar x ++ glub bar
  where
  bar x = show x

为完整起见,值得注意的是,对类型的无约束意味着没有单态性约束:

foo5 :: forall a. Show a => a -> String
foo5 x = bar x ++ glub bar
  where
  bar = const "bar"

一种相关情况涉及两次使用bar,但没有等级2功能:

foo6 x y = bar x ++ bar y
  where
  bar = show

GHC会为foo6推断哪种类型?

GHCi> :t foo6
foo6 :: Show a => a -> a -> [Char]

参数具有相同的类型,否则将需要bar的泛化,这需要类型签名(或点满度等):

foo7 x y = bar x ++ bar y
  where
  bar :: forall a. Show a => a -> String
  bar = show
GHCi> :t foo7
foo7 :: (Show a1, Show a2) => a1 -> a2 -> [Char]

由于我还没有提到它,所以这与您的第二个shallowest类似:

foo8 :: forall a. Show a => a -> String 
foo8 x = bar x
  where
  bar = show

值得强调的是,bar实际上并未在此处得到概括:它在类型变量a中是单态的。我们仍然可以通过弄乱foo7而不是bar来打破这个例子:

foo9 = bar
  where
  bar :: _
  bar = show

在这种情况下,bar没有被概括,foo也没有被概括(现在是无点的,没有签名)。这意味着永远不会解析单态类型变量。根据单态性限制的规则2,它变成了模糊的类型变量:

    <interactive>:718:14: error:
        • Found type wildcard ‘_’ standing for ‘a0 -> String’
          Where: ‘a0’ is an ambiguous type variable
          To use the inferred type, enable PartialTypeSignatures
        • In the type signature: bar :: _
          In an equation for ‘foo9’:
              foo9
                = bar
                where
                    bar :: _
                    bar = show
        • Relevant bindings include
            foo9 :: a0 -> String (bound at <interactive>:716:5)

<interactive>:719:13: error:
    • Ambiguous type variable ‘a0’ arising from a use of ‘show’
      prevents the constraint ‘(Show a0)’ from being solved.
      Relevant bindings include
        bar :: a0 -> String (bound at <interactive>:719:7)
        foo9 :: a0 -> String (bound at <interactive>:716:5)
      Probable fix: use a type annotation to specify what ‘a0’ should be.
      These potential instances exist:
        instance Show a => Show (ZipList a)
          -- Defined in ‘Control.Applicative’
        instance Show Constr -- Defined in ‘Data.Data’
        instance Show ConstrRep -- Defined in ‘Data.Data’
        ...plus 64 others
        ...plus 250 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the expression: show
      In an equation for ‘bar’: bar = show
      In an equation for ‘foo9’:
          foo9
            = bar
            where
                bar :: _
                bar = show

bar的定义中向foo9添加类型签名无济于事–只是更改了报告错误的起点。将bar更改为没有约束的内容确实可以消除错误,因为它可以归纳barfoo