[英]How can I remove all the boilerplate introduced by Trees That Grow?
我正在嘗試在 Haskell 中定義一種編程語言。我希望使 AST 可擴展:AST 模塊的用戶(例如漂亮的打印機、解釋器、編譯器、類型系統、語言服務器等)應該能夠通過添加新功能和新數據(用於擴展語法的新數據類型以及用於當前數據構造函數的新字段以存儲各種組件所需的數據)來擴展它。
我試圖通過使用Trees That Grow (TTG) 來實現這個目標。 它有效,但會導致太多的樣板文件。 我的最小原型的代碼行數增加了 10 倍,這個數字的增長量是 AST 大小乘以擴展數。 改變一些小的東西需要改變 AST 模塊的幾行,而改變實現可擴展性的方式需要重寫大部分。
有什么方法可以減少所需樣板文件的數量,或者最好將其完全刪除?
這只是 AST 的一小部分。 它與 JSON 非常相似,因為我決定從一個小原型開始。
module AST ( KeyValue(..), Data(..) ) where
data KeyValue = KV String Data deriving (Show, Eq, Ord)
data Data =
Null |
Int Int |
Num Double |
Bool Bool |
String String |
Array [Data] |
Object [KeyValue] deriving (Show, Eq, Ord)
為了通過 TTG 擴展它,數據類型變成這樣:
data KeyValueX x =
KVX (XKV x) String (DataX x) |
KeyValueX (XKeyValue x)
data DataX x =
NullX (XNull x) |
IntX (XInt x) Int |
NumX (XNum x) Double |
BoolX (XBool x) Bool |
StringX (XString x) String |
ArrayX (XArray x) [DataX x] |
ObjectX (XObject x) [KeyValueX x] |
DataX (XData x)
每個名稱以X
開頭的類型都是一個type family
:
type family XKV x
type family XKeyValue x
type family XNull x
type family XInt x
type family XNum x
type family XBool x
type family XString x
type family XArray x
type family XObject x
type family XData x
此外,它們中的每一個都需要以一種類型列出,以便更容易派生類:
type ForallX (c :: Type -> Constraint) x = (
c (XKV x), c (XKeyValue x),
c (XNull x), c (XInt x), c (XNum x), c (XBool x),
c (XString x), c (XArray x), c (XObject x), c (XData x)
)
-- now we can do:
deriving instance ForallX Show x => Show (KeyValueX x)
deriving instance ForallX Show x => Show (DataX x)
deriving instance ForallX Eq x => Eq (KeyValueX x)
deriving instance ForallX Eq x => Eq (DataX x)
deriving instance ForallX Ord x => Ord (KeyValueX x)
deriving instance ForallX Ord x => Ord (DataX x)
當然,一切都需要導出:
module AST ( KeyValueX(..), DataX(..),
XKV, XKeyValue,
XNull, XNum, XBool, XString, XArray, XObject, XData,
ForallX
) where
這是創建擴展所需要的。 即使只是需要提供的“身份”擴展(UnDecorated)。
對於每個實例,您需要為每個類型和數據構造函數的類型族實現一個類型類:
data UD -- UnDecorated, identity extension
type instance XKV UD = ()
type instance XKeyValue UD = Void
type instance XData UD = Void
type instance XNull UD = ()
type instance XInt UD = ()
type instance XNum UD = ()
type instance XBool UD = ()
type instance XString UD = ()
type instance XArray UD = ()
type instance XObject UD = ()
然后,為了正確地為用戶做事並且足夠符合人體工程學,您需要為每個數據構造函數和數據類型設置模式和類型別名:
type KeyValue = KeyValueX UD
pattern KV :: String -> Data -> KeyValue
pattern KV x y <- KVX _ x y where KV x y = KVX () x y
type Data = DataX UD
pattern Null :: Data
pattern Null <- NullX _ where Null = NullX ()
pattern DInt :: Int -> Data
pattern DInt x <- IntX _ x where DInt x = IntX () x
pattern DNum :: Double -> Data
pattern DNum x <- NumX _ x where DNum x = NumX () x
pattern DBool :: Bool -> Data
pattern DBool x <- BoolX _ x where DBool x = BoolX () x
pattern DString :: String -> Data
pattern DString x <- StringX _ x where DString x = StringX () x
pattern Array :: [Data] -> Data
pattern Array x <- ArrayX _ x where Array x = ArrayX () x
pattern Object :: [KeyValue] -> Data
pattern Object x <- ObjectX _ x where Object x = ObjectX () x
當然,所有這些東西也應該導出:
module AST ( ...,
UD,
KeyValue, Data,
pattern KV,
pattern Null, pattern Num, pattern Bool,
pattern String, pattern Array, pattern Object
) where
TTG 將我簡單的 10 行模塊變成了 100 多行的模塊,其中 90% 的代碼是無聊的、難以維護的樣板文件:
我估計整個語言可以采用幾十種類型,總共有一百多個數據構造函數。 然后我需要為 AST 定義一些擴展。 不可擴展的 AST 大約需要 100 行(作為一個數量級),而通過 TTG 擴展的 AST 大約需要 10,000 行。 所有必需的樣板文件將使所有這些對我來說難以管理。
有什么方法可以減少所需樣板文件的數量,或者最好將其完全刪除?
否則有沒有其他方法可以使我的 AST 可擴展而不需要這么多工作?
您可以將所有類型族合並為一個由符號索引的類型族:
data KeyValueX x =
KVX (X "KVX" x) String (DataX x) |
KeyValueX (X "KeyValueX" x)
deriving Generic
data DataX x =
NullX (X "NullX" x) |
IntX (X "IntX" x) Int |
NumX (X "NumX" x) Double |
BoolX (X "BoolX" x) Bool |
StringX (X "StringX" x) String |
ArrayX (X "ArrayX" x) [DataX x] |
ObjectX (X "ObjectX" x) [KeyValueX x] |
DataX (X "DataX" x)
deriving Generic
--
type family X (s :: k) (x :: l) :: Type
使用 generics 獲取所有構造函數名稱:
type ForAllX c x = (AllX c (CNames (DataX x)) x, AllX c (CNames (KeyValueX x)) x)
deriving instance ForAllX Eq x => Eq (DataX x)
deriving instance ForAllX Eq x => Eq (KeyValueX x)
-- CNames defined using generics, below
到那時為止的所有樣板也可以使用模板 Haskell 從“基本 AST”生成。
只有一個類型族使得使用包羅萬象的子句定義擴展變得容易:
data UD
type instance X s UD = XUD s
type family XUD (s :: Symbol) :: Type where
XUD "KeyValueX" = Void
XUD "DataX" = Void
XUD _ = ()
至於模式,也許只是公開構造函數並不是那么糟糕? GHC 就是這樣做的。
導入和 generics 代碼使這個答案獨立:
{-# LANGUAGE
DataKinds,
DeriveGeneric,
PolyKinds,
StandaloneDeriving,
TypeFamilies,
UndecidableInstances #-}
module T where
import Data.Kind (Constraint, Type)
import Data.Void
import GHC.Generics
import GHC.TypeLits
type CNames a = GCNames (Rep a)
type family GCNames (f :: Type -> Type) :: [Symbol] where
GCNames (M1 D c f) = GCNames f
GCNames (f :+: g) = GCNames f ++ GCNames g
GCNames (M1 C (MetaCons name _ _) f) = '[name]
type family (xs :: [k]) ++ (ys :: [k]) :: [k] where
'[] ++ ys = ys
(x ': xs) ++ ys = x ': (xs ++ ys)
type family AllX (c :: Type -> Constraint) (xs :: [Symbol]) (x :: l) :: Constraint where
AllX c '[] x = ()
AllX c (s ': ss) x = (c (X s x), AllX c ss x)
要點:https://gist.github.com/Lysxia/3f6781b3a307a7e0c564920d6277bee2
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.