[英]A simple compiler between two ASTs exposing identical language types and operations
我正在尋找一種解決以下問題的優雅方法。 歡迎所有選項,尤其是類型類和GADT :-)
場景是這樣的:存在一種具有類型(字符串和整數)和操作(+,-,++和split)的小語言。 該語言有兩種語法,每種都有自己的解析器。 我想編寫一個編譯器,它可以從語言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))
,例如ArithOpX (EAddX (yToX a) (yToX b))
。 一定會有更好的辦法?
嘗試使用以下單一類型Exp t
替換ExpX
和ExpY
。 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
是“幻影類型”的示例。 “幻像類型”是一種永遠不會出現在任何構造函數中的類型。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.