为什么这个GADT上的模式匹配似乎会在类型检查器中引入歧义?

och*_*les 8 haskell

我正在尝试实现一种抽象语法图形,如Andres Loeh和Bruno C. d所述.S. Oliveira.在大多数情况下,我似乎正确地理解了事情.但是,当我尝试将letrec引入我的语法时,我遇到了一些问题.我认为通过这个小代码示例更容易:

首先,一点前奏:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeOperators #-}
import Control.Applicative

infixr 8 :::
data TList :: (k -> *) -> [k] -> * where
  TNil :: TList f '[]
  (:::) :: f t -> TList f ts -> TList f (t ': ts)

tmap :: (forall a. f a -> g a) -> TList f as -> TList g as
tmap f (TNil) = TNil
tmap f (x ::: xs) = f x ::: tmap f xs

ttraverse :: Applicative i => (forall a. f a -> i (g a)) -> TList f xs -> i (TList g xs)
ttraverse f TNil = pure TNil
ttraverse f (x ::: xs) = (:::) <$> f x <*> ttraverse f xs
Run Code Online (Sandbox Code Playgroud)

现在我可以为我的语言定义语法.在这种情况下,我试图在我的视频游戏中描述二维水平.我有顶点(平面中的点)和墙(顶点之间的线段).

data WallId
data VertexId

data LevelExpr :: (* -> *) -> * -> * where
  Let
    :: (TList f ts -> TList (LevelExpr f) ts)
    -> (TList f ts -> LevelExpr f t)
    -> LevelExpr f t

  Var :: f t -> LevelExpr f t

  Vertex :: (Float, Float) -> LevelExpr f VertexId

  Wall
    :: LevelExpr f VertexId
    -> LevelExpr f VertexId
    -> LevelExpr f WallId
Run Code Online (Sandbox Code Playgroud)

在PHOAS之后,我们使用更高级别的类型来强制参数选择f:

newtype Level t = Level (forall f. LevelExpr f t)
Run Code Online (Sandbox Code Playgroud)

最后,让我们介绍一些语法糖letrec,自动标记所有内容Var,如论文所示:

letrec :: (TList (LevelExpr f) ts -> TList (LevelExpr f) ts)
       -> (TList (LevelExpr f) ts -> LevelExpr f t)
       -> LevelExpr f t
letrec es e =
  Let (\xs -> es (tmap Var xs))
      (\xs -> e (tmap Var xs))
Run Code Online (Sandbox Code Playgroud)

我们现在可以用这种语言编写一些程序.这是一个简单的表达式,引入两个顶点并在它们之间定义一个墙.

testExpr :: Level WallId
testExpr =
  Level (letrec (\ts ->
                   Vertex (0,0) :::
                   Vertex (10,10) :::
                   TNil)
                (\(v1 ::: v2 ::: _) ->
                   Wall v1 v2))
Run Code Online (Sandbox Code Playgroud)

这很好用.一个等价的表达式是使用letrec来定义两个顶点和它们之间的墙,都是绑定的.在letrec的主体中,我们可以返回墙壁绑定.我们首先将墙移入letrec,并添加一些洞以查看GHC知道的内容:

testExprLetrec :: Level WallId
testExprLetrec =
  Level (letrec (\ts ->
                   Vertex (0,0) :::
                   Vertex (10,10) :::
                   Wall _ _ :::
                   TNil)
                _)
Run Code Online (Sandbox Code Playgroud)

GHC通知我们:

y-u-no-infer.hs:74:25:
    Found hole `_' with type: LevelExpr f VertexId
    Where: `f' is a rigid type variable bound by
               a type expected by the context: LevelExpr f WallId
               at y-u-no-infer.hs:71:3
    Relevant bindings include
      ts :: TList (LevelExpr f) '[VertexId, VertexId, WallId]
        (bound at y-u-no-infer.hs:71:19)
      testExprLetrec :: Level WallId (bound at y-u-no-infer.hs:70:1)
    In the first argument of `Wall', namely `_'
    In the first argument of `(:::)', namely `Wall _ _'
    In the second argument of `(:::)', namely `Wall _ _ ::: TNil'
Run Code Online (Sandbox Code Playgroud)

好的,好的 - GHC知道ts包含两个VertexId和一个WallId.我们应该能够模式匹配ts以提取每个表达式.

testExprLetrec2 :: Level WallId
testExprLetrec2 =
  Level (letrec (\ts@(v1 ::: v2 ::: _) ->
                   Vertex (0,0) :::
                   Vertex (10,10) :::
                   Wall v1 v2 :::
                   TNil)
                _)
Run Code Online (Sandbox Code Playgroud)

当我尝试打字检查时,我会被提出来

y-u-no-infer.hs:109:20:
    Could not deduce (t ~ VertexId)
    from the context (ts0 ~ (t : ts))
      bound by a pattern with constructor
                 ::: :: forall (k :: BOX) (f :: k -> *) (t :: k) (ts :: [k]).
                        f t -> TList f ts -> TList f (t : ts),
               in a lambda abstraction
      at y-u-no-infer.hs:108:23-37
    or from (ts ~ (t1 : ts1))
      bound by a pattern with constructor
                 ::: :: forall (k :: BOX) (f :: k -> *) (t :: k) (ts :: [k]).
                        f t -> TList f ts -> TList f (t : ts),
               in a lambda abstraction
      at y-u-no-infer.hs:108:23-31
      `t' is a rigid type variable bound by
          a pattern with constructor
            ::: :: forall (k :: BOX) (f :: k -> *) (t :: k) (ts :: [k]).
                   f t -> TList f ts -> TList f (t : ts),
          in a lambda abstraction
          at y-u-no-infer.hs:108:23
    Expected type: TList (LevelExpr f) ts0
      Actual type: TList (LevelExpr f) '[VertexId, VertexId, WallId]
    Relevant bindings include
      v1 :: LevelExpr f t (bound at y-u-no-infer.hs:108:23)
    In the expression:
      Vertex (0, 0) ::: Vertex (10, 10) ::: Wall v1 v2 ::: TNil
    In the first argument of `letrec', namely
      `(\ ts@(v1 ::: v2 ::: _)
          -> Vertex (0, 0) ::: Vertex (10, 10) ::: Wall v1 v2 ::: TNil)'
    In the first argument of `Level', namely
      `(letrec
          (\ ts@(v1 ::: v2 ::: _)
             -> Vertex (0, 0) ::: Vertex (10, 10) ::: Wall v1 v2 ::: TNil)
          _)'
Run Code Online (Sandbox Code Playgroud)

为什么GHC现在期待a TList (LevelExpr f) ts0,当它以前是新的ts0 ~ '[VertexId, VertexId, WallId]

Rom*_*aka 4

类型推断不能与 GADT 一起可靠地工作。您可以通过提供简单的类型注释来修复代码:

testExprLetrec2 :: Level WallId
testExprLetrec2 =
  Level (letrec ((\ts@(v1 ::: v2 ::: _
                       :: TList (LevelExpr f) '[VertexId, VertexId, WallId]) ->
                   Vertex (0,0) :::
                   Vertex (10,10) :::
                   Wall _ _ :::
                   TNil)
                   )
                _)
Run Code Online (Sandbox Code Playgroud)

  • 是的,如果没有显式类型签名,则 lambda 中的模式匹配不会触发必要的细化。其他可能的解决方法:(1)使用异构列表的嵌套对样式/非 GADT 表示;(2) 使用类似`ts! Z`和`ts!SZ`代替`v1`和`v2`来选择列表元素,而不需要模式匹配。 (2认同)