如何在 GADT 制定的 AST 中指定异构集合的类型?
How to specify the type for a heterogenous collection in a GADT formulated AST?
我想为动态语言制作一个类型化的 AST。目前,我坚持处理集合。这是一个代表性的代码示例:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ExistentialQuantification #-}
data Box = forall s. B s
data BinOp = Add | Sub | Mul | Div
deriving (Eq, Show)
data Flag = Empty | NonEmpty
data List :: Flag -> * -> * where
Nil :: List Empty a
Cons :: a -> List f a -> List NonEmpty a
data Expr ty where
EInt :: Integer -> Expr Integer
EDouble :: Double -> Expr Double
-- EList :: List -> Expr List
虽然我可以很好地构建 List
的实例:
*Main> :t (Cons (B (EInt 1)) (Cons (B (EDouble 2.0)) Nil))
(Cons (B (EInt 1)) (Cons (B (EDouble 2.0)) Nil))
:: List Box 'NonEmpty
我完全不确定如何在 Expr
中为 EList
编码这种类型。我什至走在正确的道路上吗?
解决此问题的一种方法是使用 运行 时间类型代表来标记值。我在这里引导 Stephanie Weirich。让我们举个小例子。首先,给出一些类型的表示。这通常是通过 单例 构造完成的。
data Type :: * -> * where
Int :: Type Int
Char :: Type Char
List :: Type x -> Type [x]
因此 Type Int
包含一个值,我也将其称为 Int
,因为它充当类型 Int
的 运行 时间代表。如果即使在单色事物中也能看到颜色,::
左边的 Int
是红色,Type
之后的 Int
是蓝色。
现在我们可以进行存在性包装,保留效用。
data Cell :: * where
(:::) :: x -> Type x -> Cell
A Cell
是一个用 运行 时间代表其类型标记的值。您可以通过读取其类型标签来恢复该值的效用。事实上,由于类型是一阶结构,我们可以用一种有用的方式检查它们是否相等。
data EQ :: k -> k -> * where
Refl :: EQ x x
typeEQ :: Type x -> Type y -> Maybe (EQ x y)
typeEQ Int Int = Just Refl
typeEQ Char Char = Just Refl
typeEQ (List s) (List t) = case typeEQ s t of
Just Refl -> Just Refl
Nothing -> Nothing
typeEQ _ _ = Nothing
类型代表的布尔相等性没有用:我们需要相等性测试来构造所代表类型可以统一的证据。通过证据测试,我们可以写
gimme :: Type x -> Cell -> Maybe x
gimme t (x ::: s) = case typeEQ s t of
Just Refl -> Just x
Nothing -> Nothing
当然,写类型标签很麻烦。但是为什么要养条狗自己叫呢?
class TypeMe x where
myType :: Type x
instance TypeMe Int where
myType = Int
instance TypeMe Char where
myType = Char
instance TypeMe x => TypeMe [x] where
myType = List myType
cell :: TypeMe x => x -> Cell
cell x = x ::: myType
现在我们可以做
myCells :: [Cell]
myCells = [cell (length "foo"), cell "foo"]
然后得到
> gimme Int (head myCells)
Just 3
当然,如果我们不必进行单例构造并且可以只对我们可能选择在 运行 时间保留的类型进行模式匹配,那么一切都会更加整洁。我希望当神秘的 pi
量词变得不那么神秘时,我们就会到达那里。
我想为动态语言制作一个类型化的 AST。目前,我坚持处理集合。这是一个代表性的代码示例:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ExistentialQuantification #-}
data Box = forall s. B s
data BinOp = Add | Sub | Mul | Div
deriving (Eq, Show)
data Flag = Empty | NonEmpty
data List :: Flag -> * -> * where
Nil :: List Empty a
Cons :: a -> List f a -> List NonEmpty a
data Expr ty where
EInt :: Integer -> Expr Integer
EDouble :: Double -> Expr Double
-- EList :: List -> Expr List
虽然我可以很好地构建 List
的实例:
*Main> :t (Cons (B (EInt 1)) (Cons (B (EDouble 2.0)) Nil))
(Cons (B (EInt 1)) (Cons (B (EDouble 2.0)) Nil))
:: List Box 'NonEmpty
我完全不确定如何在 Expr
中为 EList
编码这种类型。我什至走在正确的道路上吗?
解决此问题的一种方法是使用 运行 时间类型代表来标记值。我在这里引导 Stephanie Weirich。让我们举个小例子。首先,给出一些类型的表示。这通常是通过 单例 构造完成的。
data Type :: * -> * where
Int :: Type Int
Char :: Type Char
List :: Type x -> Type [x]
因此 Type Int
包含一个值,我也将其称为 Int
,因为它充当类型 Int
的 运行 时间代表。如果即使在单色事物中也能看到颜色,::
左边的 Int
是红色,Type
之后的 Int
是蓝色。
现在我们可以进行存在性包装,保留效用。
data Cell :: * where
(:::) :: x -> Type x -> Cell
A Cell
是一个用 运行 时间代表其类型标记的值。您可以通过读取其类型标签来恢复该值的效用。事实上,由于类型是一阶结构,我们可以用一种有用的方式检查它们是否相等。
data EQ :: k -> k -> * where
Refl :: EQ x x
typeEQ :: Type x -> Type y -> Maybe (EQ x y)
typeEQ Int Int = Just Refl
typeEQ Char Char = Just Refl
typeEQ (List s) (List t) = case typeEQ s t of
Just Refl -> Just Refl
Nothing -> Nothing
typeEQ _ _ = Nothing
类型代表的布尔相等性没有用:我们需要相等性测试来构造所代表类型可以统一的证据。通过证据测试,我们可以写
gimme :: Type x -> Cell -> Maybe x
gimme t (x ::: s) = case typeEQ s t of
Just Refl -> Just x
Nothing -> Nothing
当然,写类型标签很麻烦。但是为什么要养条狗自己叫呢?
class TypeMe x where
myType :: Type x
instance TypeMe Int where
myType = Int
instance TypeMe Char where
myType = Char
instance TypeMe x => TypeMe [x] where
myType = List myType
cell :: TypeMe x => x -> Cell
cell x = x ::: myType
现在我们可以做
myCells :: [Cell]
myCells = [cell (length "foo"), cell "foo"]
然后得到
> gimme Int (head myCells)
Just 3
当然,如果我们不必进行单例构造并且可以只对我们可能选择在 运行 时间保留的类型进行模式匹配,那么一切都会更加整洁。我希望当神秘的 pi
量词变得不那么神秘时,我们就会到达那里。