我正在尝试将一些简单的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 #-}
. 否则,表类型类的定义失败 .
本身并不是什么大不了的事:它编译和工作,但快速阅读 MultiParamTypeClasses
暗示潜在的复杂性(我还没有花时间完全搞定它们) .
对我来说奇怪的是,这在C#中非常简单 . 假设记录/数据库类的简单不可变定义,定义接口很简单,然后实现遵循没有问题 .
interface ITable<TRecord> {
TRecord[] GetRecords(Database db);
Database SetRecords(TRecord[] records, Database db);
}
真的,这就是这个问题的本质 . 是否有更惯用的方法将上述 ITable<TRecord>
接口赋予的功能从C#转换为Haskell?我的理解是C#接口最接近Haskell类型类,所以's what I'm试图这样做 . 但我觉得很奇怪,像通用接口这样简单的东西需要在Haskell中高度吹捧的类型系统中进行语言扩展 .
(注意为什么我要这样做?为简洁起见,上面的内容有点简化,但总的来说,如果我将 Person
和 Book
实例的 Record
类型类只有 getId
,那么我可以支持CRUD函数对于整个数据库非常一般:我只需要为表类型类定义这些函数一次,它们'll apply to all tables in the DB automatically. Here'是完整代码底部的样本用法,它的C#等价.https://gist.github.com/daxfohl/a785d1ff72b921d7e90b70f625191a1c . 注意Haskell deleteRecord
不编译因为 r
的类型无法推断,而在C#中它编译得很好 . 这增加了我的想法,也许 MultiParamTypeClasses
不是正确的方法 . 但如果没有,那么是什么?)
更新
好吧,从 MultiParamTypeClasses
这样的评论中听起来很好 . 所以现在我剩下的问题是如何修复链接的要点, deleteRecord
将编译?
1 回答
这是个问题 . 在
=>
之前有一个r
但在=>
之后没有r
. 给定像deleteRecord bookTable 1 db
这样的函数调用,Haskell不知道你在谈论哪个r
. 虽然r
应完全由t
确定,但Haskell无法知道这一点 . 实际上,没有人不允许这些实例定义:“表”类型中没有任何内容可以阻止这种情况 . 它们只是没有任何实际数据的空标签 .
事实上,您的模块中只有一个感兴趣的实例是无关紧要的,Haskell无法保证其他模块的任何内容 .
那你有什么选择呢?
完全摆脱表格类型 . 记录类型就足够了 .
使用另一个Haskell扩展名
FunctionalDependencies
.使用另一个Haskell扩展,
TypeFamilies
.最后两个扩展允许创建通用容器,这是数据库表本质上的 .
以下是
Table
类型类在第一个扩展名中的显示方式:符号
t -> r
表示r
完全由t
决定(IOWr
功能取决于t
) . 一旦Haskell看到一个实例Table Foo Bar
,它就知道没有实例Table Qux Bar
(如果编译器看到冲突的定义,编译器将发出错误信号) . 这样deleteRecord
结构良好 .r
不在签名中但是没关系:t
已知且r
是t
的函数 .我让你自己弄清楚
TypeFamilies
. 如今这是一种更受欢迎的解决方案 .FunctionalDependencies
是一个较旧的扩展,它很容易理解,但在某些极端情况下会导致并发症 . 不要担心它们,在你看到之前你将成为Haskell的主人 .