DSL 的异构列表

Heterogeneous List for a DSL

TL;DR

他们是一种我可以构造一个受限于某些类型类的异构列表的方法,而无需在每个元素上调用构造函数吗?


我终于有机会更深入地研究 Haskell,并且我正在尝试构建一个 DSL,为类似 SQL 的语言生成查询字符串。以下是我尝试尝试的伪代码,一旦我更好地了解如何处理这些类型,这些类型就会更加完善。请原谅含糊不清。

我的 dsl 所需的语法类似于

from :: String -> Query
select :: [String] -> Query -> Query
select ["this", "that"] $ from "tbl"

问题是我还想在 select 的参数中允许列算术和逻辑运算符之类的东西。例如

(<~) :: String -> Column -> Column -- used for assigning a new name
add  :: String -> String -> Column

select ["stuff" <~ "this" `add` "that", "this"] $ from "tbl"

其输出可能类似于

SELECT 
    this + that as stuff, 
    this
FROM tbl;

明显的问题是这需要一个异构列表。我可以创建一个新的数据类型来包装这些值并继续我的生活,但我认为结果要麻烦得多

select ["stuff" <~ (Column "this") `add` (Column "that"), Column "this", Column "that"] $ from "tbl"

我见过一些使用 GADT 和 Existentials 的技巧,但它们都需要将每个值包装在构造函数中,而我固执地希望我的字符串保持原样。可能吗??

不,Haskell 中不可能存在适当的异构列表。您将始终需要某种构造函数。但是,您的问题可能有另一种解决方案。您可以将组合器重新定义为 return String 而不是 Column。大概是这样吧。

infixl 5 `add`
add :: String -> String -> String
add a b = a ++ " + " ++ b

infix 2 <~ 
(<~) :: String -> String -> String
new <~ old = old ++ " as " ++ new

GHC 有针对这种情况的扩展:OverloadedStrings。 OverloadedStrings 背后的基本思想是字符串文字可以是实现 Data.String.IsString 类型类的任何类型。因此,对于您的代码,您可以这样做。

import Data.String

newtype Column = Column String

instance IsString Column where
  fromString = Column

然后照原样定义组合器。现在,您想要的裸字符串文字将被解释为 Columns.

Main*> :t ["those" <~ "this" `and` "that", "thing"]
["those" <~ "this" `and` "that", "thing"] :: [Column]