我希望能够在@Daniel Wagner的优秀答案基础上提供一种略有不同的方法:与其返回一个有效类型(如果有),不如返回一个已经确定我们可以评估它的类型表达式(因为简单类型的λ演算是强正规化的)。基本思想是,如果e可以在某个上下文ctx中以t的类型进行类型标记,则check ctx t e将返回Just (ctx |- e :: t),然后给定一些已分类的表达式ctx |- e :: t,我们可以在正确类型的环境中对其进行评估。
实现
我将使用单例来模拟check :: (ctx :: [Type]) -> (a :: Type) -> Term -> Maybe (TTerm ctx a)的Pi类型,这意味着我们需要打开每个GHC扩展和厨房水槽。
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds, KindSignatures, TypeFamilies, TypeOperators #-}
{-# LANGUAGE TemplateHaskell #-}
import Data.Singletons.Prelude
import Data.Singletons.TH
import Data.Type.Equality
第一部分是未分类的表示方式,直接来自@Daniel Wagner的答案:
data Type = Base
| Arrow Type Type
deriving (Show, Eq)
data Term = Const
| Var Int
| Lam Type Term
| App Term Term
deriving Show
但我们还将为这些类型提供语义,通过将 Base
解释为()
,将 Arrow t1 t2
解释为 t1 -> t2
:
type family Interp (t :: Type) where
Interp Base = ()
Interp (Arrow t1 t2) = Interp t1 -> Interp t2
为了遵循de Bruijn主题,上下文是类型列表,变量是上下文的索引。给定上下文类型的环境,我们可以查找变量索引以获取值。请注意,lookupVar是一个全函数。
data VarIdx (ts :: [Type]) (a :: Type) where
Here :: VarIdx (a ': ts) a
There :: VarIdx ts a -> VarIdx (b ': ts) a
data Env (ts :: [Type]) where
Nil :: Env '[]
Cons :: Interp a -> Env ts -> Env (a ': ts)
lookupVar :: VarIdx ts a -> Env ts -> Interp a
lookupVar Here (Cons x _) = x
lookupVar (There v) (Cons _ xs) = lookupVar v xs
好的,我们已经准备好所有基础设施来编写一些代码。首先,让我们定义一个有类型的 Term
表示,并带有一个(完全!)评估器:
data TTerm (ctx :: [Type]) (a :: Type) where
TConst :: TTerm ctx Base
TVar :: VarIdx ctx a -> TTerm ctx a
TLam :: TTerm (a ': ctx) b -> TTerm ctx (Arrow a b)
TApp :: TTerm ctx (Arrow a b) -> TTerm ctx a -> TTerm ctx b
eval :: Env ctx -> TTerm ctx a -> Interp a
eval env TConst = ()
eval env (TVar v) = lookupVar v env
eval env (TLam lam) = \x -> eval (Cons x env) lam
eval env (TApp f e) = eval env f $ eval env e
到目前为止一切都很好。
eval
非常好用,因为它的输入只能表示简单类型lambda演算的良好类型术语。所以,@Daniel的评估器中的一部分工作将必须在将无类型表示转换为有类型表示时完成。
infer
背后的基本思想是,如果类型推断成功,则返回
Just $ TheTerm t e
,其中
t
是
e
类型的一个
Sing
leton证明。
$(genSingletons [''Type])
$(singDecideInstance ''Type)
data SomeTerm (ctx :: [Type]) where
TheTerm :: Sing a -> TTerm ctx a -> SomeTerm ctx
data SomeVar (ctx :: [Type]) where
TheVar :: Sing a -> VarIdx ctx a -> SomeVar ctx
infer :: Sing ctx -> Term -> Maybe (SomeTerm ctx)
infer _ Const = return $ TheTerm SBase TConst
infer ts (Var n) = do
TheVar t v <- inferVar ts n
return $ TheTerm t $ TVar v
infer ts (App f e) = do
TheTerm t0 e' <- infer ts e
TheTerm (SArrow t0' t) f' <- infer ts f
Refl <- testEquality t0' t0
return $ TheTerm t $ TApp f' e'
infer ts (Lam ty e) = case toSing ty of
SomeSing t0 -> do
TheTerm t e' <- infer (SCons t0 ts) e
return $ TheTerm (SArrow t0 t) $ TLam e'
inferVar :: Sing ctx -> Int -> Maybe (SomeVar ctx)
inferVar (SCons t _) 0 = return $ TheVar t Here
inferVar (SCons _ ts) n = do
TheVar t v <- inferVar ts (n-1)
return $ TheVar t $ There v
inferVar _ _ = Nothing
希望最后一步很明显:因为我们只能在给定类型下评估类型正确的术语(这就是赋予其Haskell嵌入类型的方法),所以我们将类型推断转换为类型检查:
check :: Sing ctx -> Sing a -> Term -> Maybe (TTerm ctx a)
check ctx t e = do
TheTerm t' e' <- infer ctx e
Refl <- testEquality t t'
return e'
示例会话
让我们在GHCi中尝试使用我们的函数:
λ» :set -XStandaloneDeriving -XGADTs
λ» deriving instance Show (VarIdx ctx a)
λ» deriving instance Show (TTerm ctx a)
λ» let id = Lam Base (Var 0)
λ» check SNil (SBase `SArrow` SBase) id
Just (TLam (TVar Here))
λ» let const = Lam Base $ Lam Base $ Var 1
λ» check SNil (SBase `SArrow` SBase) const
Nothing
λ» check SNil (SBase `SArrow` (SBase `SArrow` SBase)) const
Just (TLam (TLam (TVar Here)))
λ» :t eval Nil <$> check SNil (SBase `SArrow` (SBase `SArrow` SBase)) const
eval Nil <$> check SNil (SBase `SArrow` (SBase `SArrow` SBase)) const
:: Maybe (() -> () -> ())
λ» let Just const' = check SNil (SBase `SArrow` (SBase `SArrow` SBase)) const
λ» :t eval Nil const'
eval Nil const' :: () -> () -> ()
λ» eval Nil const' () ()
()
()
、元组(,)
和函数的lambda构造器,它试图推断出一个具有给定类型的对象。关于从类型中推导Haskell代码的问题,有一个相关的问题。 - Cirdecs
的最后一句话似乎表明你想要动态检查,但你应该澄清这一点。 - chi