我正在用 Haskell 撰寫決議器(主要是為了學習)。我有一個作業標記器和決議器,我想在給出錯誤訊息時添加行號。我有這種型別:
data Token = Lambda
| Dot
| LParen
| RParen
| Ident String
回到 OO 領域,我只需要創建一個 Metadata 物件來保存令牌在源代碼中的位置。所以我可以試試這個:
data Metadata = Pos String Int Int
然后,我可以更改Token為
data Token = Lambda Metadata
| Dot Metadata
| LParen Metadata
| RParen Metadata
| Ident String Metadata
但是,我的決議器是使用標記上的模式匹配撰寫的。所以現在,我所有的模式匹配都被破壞了,因為我還需要考慮元資料。所以這似乎并不理想。99% 的時間,我不關心元資料。
那么做我想做的事情的“正確”方式是什么?
uj5u.com熱心網友回復:
在 Haskell 中設計語法表示的方法有很多種,但我可以提供一些建議和推理。
建議將元資料注釋保留在型別之外Token,以便它堅持單一職責。如果 a僅Token代表一個標記,則其派生實體 for等將按預期作業,而無需擔心何時忽略注釋。Eq
值得慶幸的是,在這種情況下,替代方案很簡單。一種選擇是將注釋資訊移動到單獨的包裝器型別。
-- An @'Anno' a@ is a value of type @a@ annotated with some 'Metadata'.
data Anno a = Anno { annotation :: Metadata, item :: a }
deriving
( Eq
, Ord
, Show
-- …
)
現在標記器可以回傳一個帶注釋標記的序列,即[Annotated Token]. 您仍然需要更新使用站點,但更改現在要簡單得多。您可以通過各種方式忽略注釋:
-- Positional matching
f1 (Anno _meta (Ident name)) = …
-- Record matching
f2 Anno { item = Ident name } = …
-- With ‘NamedFieldPuns’
f3 Anno { item } = …
-- 'U'nannotated value; with ‘PatternSynonyms’
pattern U :: a -> Anno a
pattern U x <- Anno _meta x
f4 (U LParen) = …
您可以對一系列標記進行解注,fmap item以重用不關心位置資訊的現有代碼。并且由于Anno是 kind 的一種Type -> Type,GHC 還可以為它派生Foldable, Functor, and Traversable,從而可以很容易地用例如fmapand對帶注釋的專案進行操作traverse。
這是 的首選方法Token,但對于包含注釋的已決議 AST,您可能希望將注釋型別作為 AST 型別的引數,例如:
data Expr a = Add a (Expr a) (Expr a) | Literal a Int
deriving (Eq, Foldable, Functor, Ord, Show, Traversable)
然后,您可以將Expr Metadata其用于帶注釋的術語,或Expr ()用于未注釋的術語。要比較等式的術語,例如在單元測驗中,您可以使用Functor實體來去除注釋,例如void expr1 == void expr2, wherevoid等價于fmap (\ _meta -> ())here。
在更大的代碼庫中,如果有很多代碼取決于資料型別,并且您真的想避免一次全部更新,您可以將舊型別包裝在一個模塊中,該模塊為每個舊建構式匯出模式同義詞。這使您可以在洗掉配接器模塊之前逐步更新舊代碼。
從文化上講,在自包含的 Haskell 代碼庫中,典型的做法是簡單地進行重大更改,并讓編譯器在任何地方告訴你需要更新的地方,因為在高度保證正確的情況下進行廣泛的重構非常容易。當涉及到已發布的庫代碼時,我們更關心向后兼容性,因為這實際上會影響其他人。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/465873.html
標籤:哈斯克尔
上一篇:Haskell嵌入式
