ViewPatterns 以不可预测的方式影响类型检查

ViewPatterns affects typechecking in an unpredictable manner

考虑以下代码片段:

import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Vector.Unboxed as UV
import qualified Data.Vector.Generic as V

bar :: Int -> UV.Vector Char -> (Text, Text)
bar i v = (t_pre, t_post)
  where
    f = T.pack . V.toList
    (t_pre, t_post) = (\(x, y) -> (f x, f y)) $ V.splitAt i v

如您所料,编译正常。但是,如果将函数调用替换为视图模式,则会出现类型错误。

{-# LANGUAGE ViewPatterns #-}

import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Vector.Unboxed as UV
import qualified Data.Vector.Generic as V

bar :: Int -> UV.Vector Char -> (Text, Text)
bar i v = (t_pre, t_post)
  where
    f = T.pack . V.toList
    (f -> t_pre, f -> t_post) = V.splitAt i v

这将打印以下消息(-fprint-potential-instances):

    • Ambiguous type variable ‘v0’ arising from a use of ‘V.toList’
      prevents the constraint ‘(V.Vector v0 Char)’ from being solved.
      Relevant bindings include
    f :: v0 Char -> Text (bound at Weird.hs:11:5)
      Probable fix: use a type annotation to specify what ‘v0’ should be.
      These potential instances exist:
    instance V.Vector UV.Vector Char
      -- Defined in ‘Data.Vector.Unboxed.Base’
    ...plus one instance involving out-of-scope types
      instance primitive-0.6.3.0:Data.Primitive.Types.Prim a =>
               V.Vector Data.Vector.Primitive.Vector a
        -- Defined in ‘Data.Vector.Primitive’
    • In the second argument of ‘(.)’, namely ‘V.toList’
      In the expression: T.pack . V.toList
      In an equation for ‘f’: f = T.pack . V.toList
   |
11 |     f = T.pack . V.toList
   |                  ^^^^^^^^

Weird.hs:13:6: error:
    Variable not in scope: f :: UV.Vector Char -> t
   |
13 |     (f -> t_pre, f -> t_post) = V.splitAt i v
   |      ^

Weird.hs:13:18: error:
    Variable not in scope: f :: UV.Vector Char -> t1
   |
13 |     (f -> t_pre, f -> t_post) = V.splitAt i v
   |                  ^

我的理解是这两种表达方式是完全等价的,因为视图模式只是函数应用,没有命名绑定变量。我误解了视图模式吗?是不是以一种意想不到的方式与类型检查器交互的脱糖?如果我在两个调用站点内联 f 的定义,类型错误就会消失。

我已经用 GHCi 8.4.3 测试过了。


更新:这是一个编译器错误。有关详细信息,请参阅 GHC Trac #14293

这确实很奇怪。这可能是一个错误。

修改原代码如下

where
f x = T.pack (V.toList x)
(f -> t_pre, f -> t_post) = V.splitAt i v

让 GHC 请求 FlexibleContexts。这样做之后,我们得到一个非常奇怪的错误:

    Variable not in scope: f :: UV.Vector Char -> t
   |
12 |     (f -> t_pre, f -> t_post) = V.splitAt i v
   |      ^
    Variable not in scope: f :: UV.Vector Char -> t1
   |
12 |     (f -> t_pre, f -> t_post) = V.splitAt i v
   |                  ^

这对我来说似乎是一个错误。 f 应该在范围内。

f 移动到全局范围:

   ...
   where
   (f -> t_pre, f -> t_post) = V.splitAt i v

f x = T.pack (V.toList x)

代码现在可以正常工作了。如果我们将全局 f 恢复为 pointfree 定义,它甚至可以工作。

使用显式类型注释,如

where
f :: UV.Vector Char -> Text
f x = T.pack (V.toList x)
(f -> t_pre, f -> t_post) = V.splitAt i v

产生令人费解的错误消息

• Variable not in scope: f :: UV.Vector Char -> t
• Perhaps you meant ‘f’ (line 12)

我不明白到底发生了什么。在 GHCi 中,这两个都工作正常

> let f = id ; foo (f -> x) = x in foo ()
()
> let bar = foo () where {f = id ; foo (f -> x) = x} in bar
()

因此,我们可以在视图模式中使用本地 f。尽管如此,当 f 的类型需要一些更仔细的类型推断(?)时,它就不能在视图模式中使用。这看起来像一个错误。最起码报错信息应该更清楚了。

你的 f 有问题,这是由单态限制引起的。如果你 eta 扩展 f,给它一个类型签名,或者打开 NoMonomorphismRestriction,那么这个错误就会消失。

但是你仍然有这些错误,这让我很吃惊!

Weird.hs:13:6: error:
    Variable not in scope: f :: UV.Vector Char -> t
   |
13 |     (f -> t_pre, f -> t_post) = V.splitAt i v
   |      ^

Weird.hs:13:18: error:
    Variable not in scope: f :: UV.Vector Char -> t1
   |
13 |     (f -> t_pre, f -> t_post) = V.splitAt i v
   |                  ^

我想如果在同一范围内定义视图模式,它们将不起作用。为了查看视图模式是否需要是顶级的,我尝试了

bar :: Int -> UV.Vector Char -> (Text, Text)
bar i v = let (f -> t_pre, f -> t_post) = V.splitAt i v in (t_pre, t_post)
  where
    f = T.pack . V.toList

效果很好。所以我尝试了

f = T.pack . V.toList
(f -> t_pre, f -> t_post) = V.splitAt 0 UV.empty

失败,f 不在范围内。

最后,如果我将这些模式放在函数调用下

f = T.pack . V.toList
g (f -> t_pre, f -> t_post) = V.splitAt 0 UV.empty

然后又好了。所以我猜规则是 "value" 模式绑定不能使用在同一范围内定义的视图模式。我觉得很奇怪,它甚至可能是一个错误。