[英]Equivalent to C# generic interface in Haskell
我正在嘗試將一些簡單的C#代碼轉換為Haskell。 所以說我有一個簡單的不可變“數據庫”類型,它只是一個包含各種列表字段的記錄。 所以,說吧
data Person = Person { }
data Book = Book { }
data Database = Database { employees :: [Person], books :: [Book], customers :: [Person] }
現在我想創建一個類型類,表示“視圖”,或者本質上是該數據庫的“表”。
class Table r t where -- r is the record type (e.g. Person or Book)
getRecords :: t -> Database -> [r]
setRecords :: t -> [r] -> Database -> Database
然后我可以創建代表每個表的實例:
data ET = EmployeeTable
instance (Table Person) ET where
getRecords t db = employees db
setRecords t records db = Database records (books db) (customers db)
這就是我所擁有的,並且它可以工作,但{-# LANGUAGE MultiParamTypeClasses #-}
是包含{-# LANGUAGE MultiParamTypeClasses #-}
。 否則,表類型類的定義失敗。
本身並不是什么大不了的事:它可以編譯和工作,但是快速閱讀MultiParamTypeClasses
可以解決潛在的復雜問題(我還沒有花時間完全搞定它們)。
對我來說奇怪的是,這在C#中非常簡單。 假設記錄/數據庫類的簡單不可變定義,定義接口很簡單,然后實現遵循沒有問題。
interface ITable<TRecord> {
TRecord[] GetRecords(Database db);
Database SetRecords(TRecord[] records, Database db);
}
真的,這就是這個問題的本質。 是否有更慣用的方法將上述ITable<TRecord>
接口所賦予的功能從C#轉換為Haskell? 我的理解是C#接口最接近Haskell類型類,所以這就是我想要做的。 但我覺得很奇怪,像通用接口這樣簡單的東西需要在Haskell中高度吹捧的類型系統中進行語言擴展。
(NB 為什么我要這樣做?為簡潔起見,上面的內容有點簡化,但一般來說,如果我創建了一個只有getId
的Record
類型類的充實Person
和Book
實例,那么我可以支持CRUD函數對於整個數據庫非常一般:我只需要為Table類型類定義一次這些函數,它們將自動應用於DB中的所有表。這里是完整代碼底部的示例用法,以及它的C#等價物.https: deleteRecord
。注意Haskell中的deleteRecord
不能編譯,因為r
的類型無法推斷,而在C#中它編譯得很好。這增加了我的想法,也許MultiParamTypeClasses
不是正確的做法。但如果不是,那又是什么?)
好吧,它聽起來像MultiParamTypeClasses
這樣的評論很好。 所以現在我剩下的問題是如何修復鏈接的要點,以便deleteRecord
將編譯?
deleteRecord :: (Table r t) => t -> Int -> Database -> Database
這是個問題。 這里有一個r
的前=>
但沒有r
后=>
。 給定一個函數調用像deleteRecord bookTable 1 db
,哈斯克爾不知道哪些r
你在說什么。 雖然r
應該完全由t
決定,但Haskell無法知道這一點。 實際上,沒有人不允許這些實例定義:
instance Table Foo Bar
instance Table Foo Baz
instance Table Qux Bar
instance Table Qux Baz
“表”類型中沒有任何內容可以阻止這種情況。 它們只是沒有任何實際數據的空標簽。
事實上,您的模塊中只有一個感興趣的實例是無關緊要的,Haskell無法保證其他模塊的任何內容。
那你有什么選擇呢?
FunctionalDependencies
。 TypeFamilies
。 最后兩個擴展允許創建通用容器,這是數據庫表本質上的。
以下是Table
類型在第一個擴展名中的外觀:
class Record r => Table r t | t -> r where ...
符號t -> r
表示r
完全由t
確定(IOW r
功能上取決於 t
)。 一旦Haskell看到一個實例Table Foo Bar
,就會知道沒有實例Table Qux Bar
(編譯器會在看到相互沖突的定義時發出錯誤信號)。 這樣deleteRecord
就是格式良好的。 r
不在簽名中但是沒關系: t
是已知的並且r
是t
的函數。
我讓你自己弄清楚TypeFamilies
。 如今這是一種更受歡迎的解決方案。 FunctionalDependencies
是一個較舊的擴展,它易於理解,但在某些極端情況下會導致復雜化。 不要擔心它們,在你看到之前你將成為Haskell的主人。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.