简体   繁体   English

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

[英]Why does pattern matching on this GADT seem to introduce ambiguity in the type checker?

I'm trying to implement a form of Abstract Syntax Graphs, as described by Andres Loeh and Bruno C. d. 我正在尝试实现一种抽象语法图的形式,如Andres Loeh和Bruno C. d所述。 S. Oliveira . S.奥利维拉 For the most part, I seem to be understanding things correctly. 在大多数情况下,我似乎正确地理解了事情。 However, when I try and introduce letrec into my syntax, I am having some problems. 但是,当我尝试将letrec引入我的语法时,出现了一些问题。 I think it's easier to work through this small code sample: 我认为通过此小代码示例更容易进行工作:

First, a little prelude: 首先,有一些前奏:

{-# 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

Now I can define the syntax for my language. 现在,我可以为我的语言定义语法。 In this case, I'm trying to describe two-dimensional levels in my video game. 在这种情况下,我试图描述视频游戏中的二维级别。 I have vertices (points in the plane) and walls (line segments between vertices). 我有顶点(平面中的点)和墙(顶点之间的线段)。

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

Following PHOAS, we use a higher rank type to enforce parametricity over the choice of f : 在PHOAS之后,我们使用更高等级的类型来对f的选择施加参数:

newtype Level t = Level (forall f. LevelExpr f t)

Finally, let us introduce some syntactic sugar for letrec that automatically tags everything with Var , as suggested by the paper: 最后,让我们为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))

We can now write some programs in this language. 我们现在可以用这种语言编写一些程序。 Here's a simple expression introducing two vertices and defining a wall between them. 这是一个简单的表达式,引入了两个顶点并在它们之间定义了墙。

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

This works just fine. 这样很好。 An equivalent expression would be to use letrec to define two vertices and the wall between them, all bound. 一个等效的表达式是使用letrec来定义两个顶点以及它们之间的墙,并且全部绑定。 In the body of letrec, we can just return the wall binding. 在letrec的主体中,我们可以只返回墙绑定。 We start by moving the wall into the letrec, and adding some holes to see what GHC knows: 我们首先将墙移入letrec,然后添加一些孔以查看GHC知道的内容:

testExprLetrec :: Level WallId
testExprLetrec =
  Level (letrec (\ts ->
                   Vertex (0,0) :::
                   Vertex (10,10) :::
                   Wall _ _ :::
                   TNil)
                _)

GHC informs us: 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'

Ok, good - GHC knows that ts contains two VertexId s and a WallId . 好的,GHC知道ts包含两个VertexId和一个WallId We should be able to pattern match on ts to extract each of these expressions. 我们应该能够在ts上进行模式匹配以提取每个表达式。

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

When I try and type check this, I am presented with 当我尝试输入此内容时,系统会提示我

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)
          _)'

Why is GHC now expecting a TList (LevelExpr f) ts0 , when it previously new that ts0 ~ '[VertexId, VertexId, WallId] ? 为什么GHC现在期望TList (LevelExpr f) ts0 ,而以前是ts0 ~ '[VertexId, VertexId, WallId]

Type inference doesn't work reliably with GADTs. GADT无法可靠地进行类型推断。 You can fix the code by giving a simple type annotation: 您可以通过提供简单的类型注释来修复代码:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM