我想表示用于決議和列印的 PDF 檔案,并且正在努力為其尋找好的型別。
PDF 檔案包含值,它可以是文本、名稱(識別符號)、將名稱映射到值的字典,以及我在這些例子中忽略的許多其他型別。我開始的時候是這樣的:
data Value = Text String | Name String | Dictionary [(String, Value)]
instance Show Value where
show (Text text) = "(" text )"
顯示(Name name)="/" name
顯示(Dictionary entries)= "<<" unlines (showEntry <$> entries) ">>" where
showEntry (key, value) = show (Name key) " "/span> show value
不幸的是,showEntry很容易意外地只使用show key,甚至show (Text key)。型別系統對挑選正確的實作沒有幫助。字典中的鍵是名字的事實并沒有被它們的型別所捕捉到,它們的型別只是 String.
我們可以通過將鍵值建模為值來解決這個問題:
data Value = Text String | Name String | Dictionary [(Value, Value)]
instance Show Value where
show (Text text) = "(" text )"
顯示(Name name)="/" name
顯示(Dictionary entries)= "<<" unlines (showEntry <$> entries) ">>" where
showEntry (key, value) = show key " "/span> show value
這樣一來,showEntry就會得到一個key型別的Value,從而自動使用正確的實作。然而,這可以說是更糟糕的,因為現在無效的 Dictionary 值的鍵不是名字,可以被表示出來。
我的下一個想法是使用獨立的型別。就像名字被用于字典中一樣,文本和字典也被用于其他資料結構中,所以它們也應該有自己的型別:
我的下一個想法是使用獨立的型別。
data Text = Text String
data Name = Name String
data Dictionary = Dictionary [(Name, Value)]
data Value = TextValue Text | NameValue Name | DictionaryValue Dictionary
instance Show Text where show (Text text) = "(" text )"
instance Show Name where show (Name name) = "/"/span> name
instance Show Dictionary where
show (Dictionary entries) = "<<" unlines (showEntry <$> entries) ">> " where
showEntry (key, value) = show key " " show value
instance Show Value where>
顯示(TextValue text)=顯示文本
顯示(NameValue name)=顯示名稱
顯示(DictionaryValue dictionary)=顯示dictionary
現在的型別準確地代表了資料的結構,一切都很好。不幸的是,這感覺非常難看和多余。為了構造值,我們現在需要兩倍的建構式:
DictionaryValue (Dictionary [(Name "foo", TextValue (Text "bar")), (Name "test", NameValue (Name "baz")) ])
感覺ADT中的標簽聯合在這里只是礙事,因為標簽是不需要的,型別已經唯一地決定了哪種情況被選擇。
這就是我們能做的最好的事情,還是說對于這種情況有更好的方法?
我想在決議允許嵌套值的格式(如算術運算式、XML、JSON、DSL 等)時,這類問題總是會出現。人們在這方面使用的標準/慣用表示法是什么?
uj5u.com熱心網友回復:
我的第一個建議是不要使用show來做這個。一般來說,讓show產生除了有效的Haskell代碼之外的任何東西都是一個壞主意,如果傳遞給read,它將復制其輸入。使用一個新的函式,例如pprintValue,而不是Show,已經解決了你的幾個問題。現在不可能在一個字串上意外地呼叫pprintValue,因為它現在是一個具體型別Value -> String的函式,而不是一個多型型別。
在做了這些之后,我實際上至少會做一些你的第二個片段建議的事情。
newtype Name = MkName String<
newtype Text = MkText String
對于Dictionary,我可能不會去打擾。你不需要很多幫助來消除歧義,而且任何正確型別的串列似乎都是構建 Dictionary Value 的安全方式。你抱怨說這 "花費 "了一些額外的建構式呼叫。這是真的,但并不像你說的那么糟糕。是的,當從頭開始創建一個 Value 時,你必須再寫一個或兩個新型別的建構式。但你有多少次這樣做呢?可能只在一兩個地方,即你決議一個檔案或決定你的服務器應該提供的回應。通常情況下,你已經有了一個Value,而且它的東西已經被適當地包裝起來了。例如,你可能想添加到一個字典的值中:
insert :: Name -> Value -> [(Name, Value)] -> [(Name, Value)]
insert n v d = (n, v) : d
盡管如此,你最終還是會做一些新型別的包裝和解包。但它仍然是有用的,可以幫助你確保你以正確的方式使用它們。這將有助于你的整個程式,而不僅僅是在呼叫show(或pprintValue)。
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/314855.html
標籤:
