使用 GADT 进行类型推断

Type inference with GADTs

在下面的代码中,我试图在 GADT 构造函数 Cons 上进行匹配,以使编译器看到 xs 是非空的:

{-# LANGUAGE DataKinds           #-} 
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE KindSignatures      #-} 
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators       #-}

import Data.Typeable

data Foo (ts :: [*]) where
  Nil :: Foo '[]
  Cons :: (Typeable t) => Foo ts -> Foo ( t ': ts)

foo :: Foo xs -> IO ()
foo Nil = print "done"
foo (Cons rest :: Foo (y ': ys)) = do
  print $ show $ typeRep (Proxy::Proxy y)
  foo rest

不幸的是,这个简单的示例无法使用 GHC 8 进行编译:

• Couldn't match type ‘xs’ with ‘y : ys’
  ‘xs’ is a rigid type variable bound by
    the type signature for:
      foo :: forall (xs :: [*]). Foo xs -> IO ()
  Expected type: Foo (y : ys)
    Actual type: Foo xs
• When checking that the pattern signature: Foo (y : ys)
    fits the type of its context: Foo xs
  In the pattern: Cons rest :: Foo (y : ys)
  In an equation for ‘foo’:
      foo (Cons rest :: Foo (y : ys))
        = print $ (show $ typeRep (Proxy :: Proxy y))

我知道 GADT 的类型推断可能很棘手(例如,#9695, #10195, #10338),但这是所以简单...

我需要做什么才能让 GHC 相信当我匹配 Cons 时,GADT 参数至少有一个元素?

你只需要一个从Foo (t ': ts)中提取Proxy t的函数:

fooFstType :: Foo (t ': ts) -> Proxy t 
fooFstType _ = Proxy 

请注意,由于 Foo 的类型参数是 t ': ts,而不仅仅是 ts,您可以引用表示类型签名中第一个元素的类型变量(相反在 body 中,以某种方式,与 ScopedTypeVariables)。

你的函数变成了

foo :: Foo xs -> IO ()
foo Nil = print "done"
foo f@(Cons rest) = do
  print $ show $ typeRep (fooFstType f)
  foo rest

另一种可能性是将作品移动到类型级别:

type family First (xs :: [k]) :: k where 
  First (x ': xs) = x 

foo :: forall xs . Foo xs -> IO ()
foo Nil = print "done"
foo (Cons rest) = do
  print $ show $ typeRep (Proxy :: Proxy (First xs))
  foo rest