如何用 GADT 表示变量类型的构造函数列表?
How to represent a variable-typed constructor list with GADTs?
我正在为我参与的研究项目实现一个小型 DSL。由于这是几乎所有对 GADT 的解释的示例,我认为现在是真正开始使用它们的好时机。显而易见的表达式(算术等)运行良好,但我的 DSL 需要支持用户定义的函数,这是我 运行 遇到问题的地方。
没有 GADT,我的表达式类型的结构看起来有点像这样(浓缩为一个最小的例子):
data Expr = IntConst Int
| BoolConst Bool
| Plus Expr Expr
| Times Expr Expr -- etc
| AndAlso Expr Expr -- etc
| Call String [Expr] -- function call!
转换为GADT,如何表达Call
规则?
data Expr a where
IntConst :: Int -> Expr Int
BoolConst :: Bool -> Expr Bool
Plus :: Expr Int -> Expr Int -> Expr Int
Times :: Expr Int -> Expr Int -> Expr Int
AndAlso :: Expr Bool -> Expr Bool -> Expr Bool
Call :: ???
我的第一个想法是,如果没有一些超级奇特的依赖类型,这是不可能的,因为没有办法知道给定的用户函数将接受什么类型(也是 return 类型,但我可以通过制作 Call
return Expr a
并专注于建筑工地)。我可以通过添加规则
强制它按"erasing"类型进行类型检查
EraseInt :: Expr Int -> Expr a
EraseBool :: Expr Bool -> Expr a
但后来我似乎失去了拥有 GADT 的好处。我也在想可能还有一些其他的 rankN
多态性我可以在 Call
中使用(某种存在类型?),但我想出的任何东西似乎都不起作用。
帮忙?
您可以使用类型级列表创建另一个具有表示参数列表的 GADT,如下所示:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE PatternSynonyms #-}
data ArgList (as :: [*]) where
NilArgList :: ArgList '[]
ConsArg :: Expr a -> ArgList as -> ArgList (a ': as)
data Expr a where
IntConst :: Int -> Expr Int
BoolConst :: Bool -> Expr Bool
Plus :: Expr Int -> Expr Int -> Expr Int
Times :: Expr Int -> Expr Int -> Expr Int
AndAlso :: Expr Bool -> Expr Bool -> Expr Bool
Call :: String -> ArgList as -> Expr b
pattern x :^ y = ConsArg x y
infixr :^
example :: Expr Int
example =
Call "exampleFn" (IntConst 1 :^ BoolConst True :^ NilArgList
:: ArgList [Int, Bool])
您需要为参数列表提供一些明确的类型签名(如 example
)。此外,Call
(forall b. Expr b
) 的结果类型有点尴尬,但我不确定是否可以避免这种情况,除非你拥有比 String
更多的函数类型信息in。如果您在那里有更多信息,您还可以将参数类型 (as
) 与函数的预期参数类型相关联。我认为我们需要有关具体情况的更多详细信息,但您必须进一步了解该部分。
也许你不需要为了你想要的东西而走依赖路线。将构建函数和调用函数分开的解决方案怎么样?这允许您在构建函数时键入函数,因此您可以进行类型检查调用。
data Expr a where
IntConst :: Int -> Expr Int
BoolConst :: Bool -> Expr Bool
Plus :: Expr Int -> Expr Int -> Expr Int
Times :: Expr Int -> Expr Int -> Expr Int
AndAlso :: Expr Bool -> Expr Bool -> Expr Bool
Fun :: String -> Expr (a->b)
Call :: Expr (a->b) -> Expr a -> Expr b
-- type checks
test = Call (Fun "f" :: Expr (Int -> Int)) (IntConst 1)
-- doesn't type check
test' = Call (Fun "f" :: Expr (Int -> Int)) (BoolConst False)
对于具有多个参数的函数,您可以通过多次调用以柯里化的方式工作,即
Call (Call (Fun "f" :: Expr (Int->Int->Int)) (IntConst 1)) (IntConst 1)
或者您可以用您的语言实现元组。
我正在为我参与的研究项目实现一个小型 DSL。由于这是几乎所有对 GADT 的解释的示例,我认为现在是真正开始使用它们的好时机。显而易见的表达式(算术等)运行良好,但我的 DSL 需要支持用户定义的函数,这是我 运行 遇到问题的地方。
没有 GADT,我的表达式类型的结构看起来有点像这样(浓缩为一个最小的例子):
data Expr = IntConst Int
| BoolConst Bool
| Plus Expr Expr
| Times Expr Expr -- etc
| AndAlso Expr Expr -- etc
| Call String [Expr] -- function call!
转换为GADT,如何表达Call
规则?
data Expr a where
IntConst :: Int -> Expr Int
BoolConst :: Bool -> Expr Bool
Plus :: Expr Int -> Expr Int -> Expr Int
Times :: Expr Int -> Expr Int -> Expr Int
AndAlso :: Expr Bool -> Expr Bool -> Expr Bool
Call :: ???
我的第一个想法是,如果没有一些超级奇特的依赖类型,这是不可能的,因为没有办法知道给定的用户函数将接受什么类型(也是 return 类型,但我可以通过制作 Call
return Expr a
并专注于建筑工地)。我可以通过添加规则
EraseInt :: Expr Int -> Expr a
EraseBool :: Expr Bool -> Expr a
但后来我似乎失去了拥有 GADT 的好处。我也在想可能还有一些其他的 rankN
多态性我可以在 Call
中使用(某种存在类型?),但我想出的任何东西似乎都不起作用。
帮忙?
您可以使用类型级列表创建另一个具有表示参数列表的 GADT,如下所示:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE PatternSynonyms #-}
data ArgList (as :: [*]) where
NilArgList :: ArgList '[]
ConsArg :: Expr a -> ArgList as -> ArgList (a ': as)
data Expr a where
IntConst :: Int -> Expr Int
BoolConst :: Bool -> Expr Bool
Plus :: Expr Int -> Expr Int -> Expr Int
Times :: Expr Int -> Expr Int -> Expr Int
AndAlso :: Expr Bool -> Expr Bool -> Expr Bool
Call :: String -> ArgList as -> Expr b
pattern x :^ y = ConsArg x y
infixr :^
example :: Expr Int
example =
Call "exampleFn" (IntConst 1 :^ BoolConst True :^ NilArgList
:: ArgList [Int, Bool])
您需要为参数列表提供一些明确的类型签名(如 example
)。此外,Call
(forall b. Expr b
) 的结果类型有点尴尬,但我不确定是否可以避免这种情况,除非你拥有比 String
更多的函数类型信息in。如果您在那里有更多信息,您还可以将参数类型 (as
) 与函数的预期参数类型相关联。我认为我们需要有关具体情况的更多详细信息,但您必须进一步了解该部分。
也许你不需要为了你想要的东西而走依赖路线。将构建函数和调用函数分开的解决方案怎么样?这允许您在构建函数时键入函数,因此您可以进行类型检查调用。
data Expr a where
IntConst :: Int -> Expr Int
BoolConst :: Bool -> Expr Bool
Plus :: Expr Int -> Expr Int -> Expr Int
Times :: Expr Int -> Expr Int -> Expr Int
AndAlso :: Expr Bool -> Expr Bool -> Expr Bool
Fun :: String -> Expr (a->b)
Call :: Expr (a->b) -> Expr a -> Expr b
-- type checks
test = Call (Fun "f" :: Expr (Int -> Int)) (IntConst 1)
-- doesn't type check
test' = Call (Fun "f" :: Expr (Int -> Int)) (BoolConst False)
对于具有多个参数的函数,您可以通过多次调用以柯里化的方式工作,即
Call (Call (Fun "f" :: Expr (Int->Int->Int)) (IntConst 1)) (IntConst 1)
或者您可以用您的语言实现元组。