【问题标题】:Inferred generic function typechecks as a return type but not an argument type将泛型函数类型检查推断为返回类型但不是参数类型
【发布时间】:2019-05-27 11:06:27
【问题描述】:

我正在学习 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 绑定的模糊性的明确错误,类似于“无法推断(Typeable a0)因使用'mkQ'而产生”;我也没有想到找出为什么上面的表格没有)。

当我们在 let 块中为 op :: GenericQ (f a) 添加注释时,类型错误消失了(需要 ScopedTypeVariables)。

但是,我很困惑,似乎op 上的Data 约束可以被推断出来:当它是返回类型时,以下类型检查:

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

有什么区别?两种情况都要求op 为forall d. Data d =&gt; d -&gt; f a;我看到的唯一区别是第一个在参数位置,第二个在返回位置。

【问题讨论】:

    标签: haskell generic-programming monomorphism-restriction


    【解决方案1】:

    在您的第二个 sn-p 中,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 通过发生在顶层的泛化 步骤变成多态的。如果在类型变量x, a, f, ... 的上下文中,shallowest 的主体具有类型x -&gt; f a,那么我们可以“关闭上下文”并将类型变量移动到shallowest :: forall x a f. x -&gt; f a 的类型中。类型推导如下:

         x, a, f |- (let op = ... in op) :: x -> f a
     ⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻ (generalization)
       |- (let op = .... in op) :: forall x a f. x -> f a
    

    (类型类和统一算法使事情变得更加复杂,但这与此答案无关。)

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

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

    • 函数定义,即let 和具有至少一个显式参数的顶级绑定(这里是单态限制);

    • 高阶函数的多态参数:如果你有一个函数f :: (forall a. w a) -&gt; r,那么f x将在检查x时泛化a;

    • 当然,当由显式注释 _ :: forall a. t a 指示时。

    【讨论】:

      【解决方案2】:

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

      • type GenericQ r = forall a . Data a =&gt; a -&gt; rfrom syb,和
      • gmapQ :: Data a =&gt; (forall d. Data d =&gt; d -&gt; u) -&gt; a -&gt; [u]from Data.Data.

      如果我对此有误,请告诉我。此外,下文中的任何foralls 都将被明确写入。


      这里有很多东西。 As Li-yao Xia suggests,这是涉及op类型的泛化问题。关于您对shallowest 的第一个定义,有三个相关事实:

      1. 在泛化之前,op 的推断类型是Data d =&gt; d -&gt; f a。给定Data d约束,单态约束规则1(见subsection 4.5.5 of the Report)意味着这种类型的d不能泛化。

      2. 在shallowest 的正文中,op 出现在两个地方。第一个是op z,z :: a1 在顶层被shallowest 的签名约束和约束。结果是op 的出现不需要参数类型的泛化:就它而言,op 的类型可以是forall f a. a1 -&gt; f a,在类型变量中是单态的a1(这个术语来自subsection 4.5.4 of the Report)。

      3. 不过,另一种情况是gmapQ op z。 gmapQ 具有 rank-2 类型,需要多态参数。既然如此,这种情况需要泛化 op 的参数类型,正如夏立尧回答末尾所指出的那样。

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


      下面是一个更简单的扩展示例,它可能有助于了解发生了什么。 (如果你要将以下 sn-ps 放入 GHCi,除了-XRankNTypes,你还应该设置-XMonomorphismRestriction 和-XNoExtendedDefaultRules 才能看到相同的结果。)

      这是一个rank-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 -&gt; String”是如何与a 被foo2 的类型签名绑定的单独事实声明的。我相信这对应于我在上面第 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 两次,但没有rank-2 函数:

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

      GHC 会为 foo6 推断出哪种类型?

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

      参数获得相同的类型,否则需要泛化bar,这需要类型签名(或pointfullness等):

      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
      

      在foo9 的定义中将类型签名添加到bar 将无济于事——它只会更改报告错误的起点。将bar 更改为没有约束的东西确实消除了错误,因为它可以概括bar 和foo。

      【讨论】:

        猜你喜欢
        • 2020-07-10
        • 2020-01-23
        • 1970-01-01
        • 2019-02-17
        • 2016-07-23
        • 1970-01-01
        • 2020-03-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多