我正在编写一个编译器,我正在为我的IR使用GADT,但是我的其他所有东西都使用标准数据类型。我在从旧数据类型转换为GADT期间遇到了麻烦。我试图用下面的小/简化语言重新创建这种情况。
首先,我有以下数据类型:
data OldLVal = VarOL Int -- The nth variable. Can be used to construct a Temp later.
| LDeref OldLVal
data Exp = Var Int -- See above
| IntT Int32
| Deref Exp
data Statement = AssignStmt OldLVal Exp
| ...
我想将这些转换为这种中间形式:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
-- Note: this is a Phantom type
data Temp a = Temp Int
data Type = IntT
| PtrT Type
data Command where
Assign :: NewLVal a -> Pure a -> Command
...
data NewLVal :: Type -> * where
VarNL :: Temp a -> NewLVal a
DerefNL :: NewLVal ('PtrT ('Just a)) -> NewLVal a
data Pure :: Type -> * where
ConstP :: Int32 -> Pure 'IntT
ConstPtrP :: Int32 -> Pure ('PtrT a)
VarP :: Temp a -> Pure a
此时,我只想编写从旧数据类型到新GADT的转换。现在,我有一些看起来像这样的东西。
convert :: Statement -> Either String Command
convert (AssignStmt oldLval exp) = do
newLval <- convertLVal oldLval -- Either String (NewLVal a)
pure <- convertPure exp -- Either String (Pure b)
-- return $ Assign newLval pure -- Obvious failure. Can't ensure a ~ b.
pure' <- matchType newLval pure -- Either String (Pure a)
return $ Assign newLval pure'
-- Converts Pure b into Pure a. Should essentially be a noop, but simply
-- proves that it is possible.
matchType :: NewLVal a -> Pure b -> Either String (Pure a)
matchType = undefined
我意识到我无法轻易地写convert
,所以我尝试使用matchType
的这个概念来解决这个问题,这个概念可以证明这两种类型确实是相同的。问题是:我如何写matchType
?如果我有完全依赖类型(或者我被告知),这会容易得多,但我可以在这里完成这段代码吗?
另一种方法是以某种方式提供newLval
作为convertPure
的参数,但我认为这本质上只是尝试使用依赖类型。
欢迎任何其他建议。
如果有帮助,我还有一个可以将Exp
或OldLVal
转换为其类型的函数:
class Typed a where
typeOf :: a -> Type
instance Typed Exp where
...
instance Typed OldLVal where
...
修改
感谢下面的优秀答案,我已经能够完成这个模块的编写。
我最终使用了下面提到的the singletons
package。起初有点奇怪,但我发现在我开始理解我在做什么之后使用它是非常合理的。但是,我确实陷入了一个陷阱:convertLVal
和convertPure
的类型需要一个存在主义来表达。
data WrappedPure = forall a. WrappedPure (Pure a, SType a)
data WrappedLVal = forall a. WrappedLVal (NewLVal a, SType a)
convertPure :: Exp -> Either String WrappedPure
convertLVal :: OldLVal -> Either String WrappedLVal
这意味着您必须在convert
中展开该存在主义,否则,下面的答案将向您展示道路。非常感谢你。
答案 0 :(得分:5)
您希望在运行时对某些类型级别的数据(即用于索引值的Type
)执行比较。但是当您运行代码并且值开始交互时,类型早已不复存在。它们以生成高效代码的名义被编译器擦除。因此,您需要手动重建已删除的类型级别数据,并使用一个值来提醒您忘记您正在查看的类型。您需要Type
的单件副本。
data SType t where
SIntT :: SType IntT
SPtrT :: SType t -> SType (PtrT t)
SType
的成员看起来像Type
的成员 - 将SPtrT (SPtrT SIntT)
之类的值的结构与PtrT (PtrT IntT)
的结构进行比较 - 但是它们被(类型)编入索引-level)Type
他们相似。对于每个t :: Type
,只有一个SType t
(因此名称为 singleton ),并且因为SType
是GADT,SType t
上的模式匹配告诉关于t
的类型检查器。单身人士跨越了严格执行的类型和价值观之间的分离。
因此,当您构建类型化树时,需要跟踪值的运行时SType
并在必要时进行比较。 (这基本上等于编写部分验证的类型检查器。)a class in Data.Type.Equality
包含一个比较两个单例并告诉您它们的索引是否匹配的函数。
instance TestEquality SType where
-- testEquality :: SType t1 -> SType t2 -> Maybe (t1 :~: t2)
testEquality SIntT SIntT = Just Refl
testEquality (SPtrT t1) (SPtrT t2)
| Just Refl <- testEquality t1 t2 = Just Refl
testEquality _ _ = Nothing
在convert
函数中应用此内容大致如下:
convert :: Statement -> Either String Command
convert (AssignStmt oldLval exp) = do
(newLval, newLValSType) <- convertLVal oldLval
(pure, pureSType) <- convertPure exp
case testEquality newLValSType pureSType of
Just Refl -> return $ Assign newLval pure'
Nothing -> Left "type mismatch"
实际上并没有很多依赖类型的程序你不能伪造TypeInType
和单身人士(有没有?),但在两者中复制所有数据类型真的很麻烦正常的“和”单身“形式。 (如果你想隐含地传递单身人士,复制会变得更糟 - 请参阅Hasochism了解详细信息。)The singletons
package可以为你生成很多样板,但它并没有真正减轻造成的痛苦通过复制概念本身。这就是为什么人们想要为Haskell添加真正的依赖类型,但是我们还有好几年的时间。
The new Type.Reflection
module包含重写的Typeable
类。它的TypeRep
类似于GADT,可以作为一种“通用单身人士”。但在我看来,使用它进行编程比使用单例编程更加尴尬。
答案 1 :(得分:2)
matchType
作为书面无法实现,但您想要的想法绝对可行。你知道Data.Typeable
吗? Typeable
是一个为检查类型提供一些基本反射操作的类。要使用它,您需要在范围内Typeable a
约束您想要了解的任何类型变量a
。所以对于matchType
你会有
matchType :: (Typeable a, Typeable b) => NewLVal a -> Pure b -> Either String (Pure a)
每当你想隐藏一个类型变量时,它还需要感染你的GADT:
data Command where
Assign :: (Typeable a) => NewLVal a -> Pure a -> Command
...
但是如果在范围内有适当的约束,则可以使用eqT
进行类型安全的运行时类型比较。例如
-- using ScopedTypeVariables and TypeApplications
matchType :: forall a b. (Typeable a, Typeable b) => NewLVal a -> Pure b -> Either String (Pure b)
matchType = case eqT @a @b of
Nothing -> Left "types are not equal"
Just Refl -> {- in this scope the compiler knows that
a and b are the same type -}