我正在寻找一种优雅的方法来解决下面的问题。欢迎所有选项,特别是类型类和GADT: - )
场景是这样的:存在一种带有类型(字符串和句点)和操作(+, - ,++和拆分)的小语言。语言有两种语法,每种语法都有自己的解析器。我想编写一个可以从语言X到语言Y,或从Y到X的编译器。从一个编译到另一个是一个直接映射到表达式列表中的一个:
xToY :: ExpX -> ExpY
yToX :: ExpY -> ExpX
..然后是show
[ExpY]
或[ExpX]
。下面是这两个编译器函数的简单实现,使用构造函数上的常规数据定义和模式匹配:
{-# LANGUAGE LambdaCase #-} module Compiler where data ExpX = StringX String | IntX Int | ArithOpX ArithExpX | StringOpX StringExpX deriving (Show) data ArithExpX = EAddX ExpX ExpX | EMinusX ExpX ExpX deriving (Show) data StringExpX = EAppendX ExpX ExpX | ESplitX ExpX ExpX deriving (Show) data ExpY = StringY String | IntY Int | ArithOpY ArithExpY | StringOpY StringExpY deriving (Show) data ArithExpY = EAddY ExpY ExpY | EMinusY ExpY ExpY deriving (Show) data StringExpY = EAppendY ExpY ExpY | ESplitY ExpY ExpY deriving (Show) xToY :: ExpX -> ExpY xToY = \case StringX s -> StringY s IntX i -> IntY i ArithOpX (EAddX a b) -> ArithOpY (EAddY (xToY a) (xToY b)) ArithOpX (EMinusX a b) -> ArithOpY (EMinusY (xToY a) (xToY b)) StringOpX (EAppendX a b) -> StringOpY (EAppendY (xToY a) (xToY b)) StringOpX (ESplitX a b) -> StringOpY (ESplitY (xToY a) (xToY b)) yToX :: ExpY -> ExpX yToX = \case StringY s -> StringX s IntY i -> IntX i ArithOpY (EAddY a b) -> ArithOpX (EAddX (yToX a) (yToX b)) ArithOpY (EMinusY a b) -> ArithOpX (EMinusX (yToX a) (yToX b)) StringOpY (EAppendY a b) -> StringOpX (EAppendX (yToX a) (yToX b)) StringOpY (ESplitY a b) -> StringOpX (ESplitX (yToX a) (yToX b))
测试noddy编译器:
*Compiler> xToY (ArithOpX (EAddX (IntX 2) (IntX 5))) ArithOpY (EAddY (IntY 2) (IntY 5)) *Compiler> yToX (StringOpY (ESplitY (StringY "foo") (StringY "bar"))) StringOpX (ESplitX (StringX "foo") (StringX "bar"))
所以它有效。不幸的是,代码重复很多,并且模式显然正在出现。我想采用Haskell更优雅的功能来实现xToY
和yToX
给出的相同结果。特别是,我正在寻找一种在构造函数之间定义二元性的方法,例如StringX s
被编译为StringY s
,而StringY s
被编译回StringX s
。当然,有一种很好的表达方式吗?此外,案例匹配右侧的嵌套xToY
和yToX
调用看起来很脏,例如ArithOpX (EAddX (yToX a) (yToX b))
。必须有更好的方法吗?
答案 0 :(得分:3)
尝试使用以下单一类型ExpX
替换ExpY
和Exp t
。 t
是一个由某种类型替换的标记,用于将其标记为特定用途:
data Exp t = String String | Int Int | ArithOp (ArithExp t) | StringOp (StringExp t) deriving (Show)
data ArithExp t = EAdd (Exp t) (Exp t) | EMinus (Exp t) (Exp t) deriving (Show)
data StringExp t = EAppend (Exp t) (Exp t) | ESplit (Exp t) (Exp t) deriving (Show)
data ForX = ForX
data ForY = ForY
然后使用Exp ForX
代替ExpX
和Exp ForY
代替ExpY
,无论您何时关心差异。
然后,您可以编写适用于forall
个标记的函数。例如,我们可以使用单个函数xToY
替换yToX
和retag
:
retag:: Exp t1 -> Exp t2
retag =
\case
String s -> String s
Int i -> Int i
ArithOp (EAdd a b) -> ArithOp (EAdd (retag a) (retag b))
ArithOp (EMinus a b) -> ArithOp (EMinus (retag a) (retag b))
StringOp (EAppend a b) -> StringOp (EAppend (retag a) (retag b))
StringOp (ESplit a b) -> StringOp (ESplit (retag a) (retag b))
此类型t
是"幻像类型"的示例。 A"幻象类型"是一种永远不会出现在任何构造函数中的类型。