我正在写一个SQL解释器。我需要在编译时区分不正确的表达式和运行时错误。
我将举例说明一些应该是格式正确的内容,但可能会在运行时失败。
SELECT $ [ColumnName "first_name" `AS` "name"] `FROM` TABLE "people.csv" `WHERE` (ColumnName "age" `Gte` LiteralInt 40)
我想专注于表达:
(ColumnName "age" `Gte` LiteralInt 40)
这应该通过类型检查器。但是,说“年龄”并不包含可以表示为LiteralInt
的内容。
所以我希望Gte
能够生成类似IO Bool
的东西(暂不考虑现在的异常处理)。
但我并不总是需要Gte
来制作IO Bool
。如果我有这样的事情:
(LiteralInt 40 `Gte` LiteralInt 10)
我只需要一个Bool。
或类似的东西:
(LiteralInt 40 `Gte` LiteralBool True)
需要在编译时失败。
所以,我一直在玩弄数据系列和GADT,并且已经陷入了许多死胡同,如果我解释它们就会混淆这种情况。
我的问题是否有意义,如果有的话,我可以通过哪种方式进行调查,从而找到解决方案?
答案 0 :(得分:7)
所以我希望
Gte
能够生成类似IO Bool
的东西(暂不考虑现在的异常处理)。但我并不总是需要
Gte
来制作IO Bool
。
这是不可能的(也不是可取的)。 Gte
必须始终返回相同的类型。此外,您可能希望将查询的构造与其执行分开......
或类似的东西:
(LiteralInt 40 `Gte` LiteralBool True)
需要在编译时失败。
现在 更合理。如果您决定沿着这条路走下去,您甚至可以使用新的TypeError
功能自定义GHC报告的类型错误。但是,坚持只涉及LiteralInt
,LiteralBool
和Gte
的简单示例,只需使用GADT即可执行以下操作:
{-# LANGUAGE GADTs #-}
data Expr a where
LiteralInt :: Int -> Expr Int
LiteralBool :: Bool -> Expr Bool
Gte :: Expr Int -> Expr Int -> Expr Bool
Add :: Expr Int -> Expr Int -> Expr Int
ColumnName :: String -> Expr a
然后,以下将全部编译:
ColumnName "age" `Gte` LiteralInt 40
LiteralInt 40 `Gte` LiteralInt 10
(LiteralInt 40 `Add` ColumnName "age") `Gte` LiteralInt 10
而以下情况不会:
LiteralInt 40 `Gte` LiteralBool True
LiteralInt 40 `Add` LiteralBool True
但是,说“年龄”不包含可以表示为
LiteralInt
的内容。
如果你在编译时知道你的架构并且想要做很多类型hackery,你可能可能会使这也成为编译时间。更简单的解决方案就是在执行查询时进行错误处理。所以你的功能看起来像
evalExpr :: Expr a -> ExceptT e IO a
你可能会在这里对列的类型进行适当的检查。