[英]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.