首页 文章

相当于Haskell中的C#通用接口

提问于
浏览
1

我正在尝试将一些简单的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中高度吹捧的类型系统中进行语言扩展 .

(注意为什么我要这样做?为简洁起见,上面的内容有点简化,但总的来说,如果我将 PersonBook 实例的 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 回答

  • 3
    deleteRecord :: (Table r t) => t -> Int -> Database -> Database
    

    这是个问题 . 在 => 之前有一个 r 但在 => 之后没有 r . 给定像 deleteRecord bookTable 1 db 这样的函数调用,Haskell不知道你在谈论哪个 r . 虽然 r 应完全由 t 确定,但Haskell无法知道这一点 . 实际上,没有人不允许这些实例定义:

    instance Table Foo Bar
    instance Table Foo Baz
    instance Table Qux Bar
    instance Table Qux Baz
    

    “表”类型中没有任何内容可以阻止这种情况 . 它们只是没有任何实际数据的空标签 .

    事实上,您的模块中只有一个感兴趣的实例是无关紧要的,Haskell无法保证其他模块的任何内容 .

    那你有什么选择呢?

    • 完全摆脱表格类型 . 记录类型就足够了 .

    • 使用另一个Haskell扩展名 FunctionalDependencies .

    • 使用另一个Haskell扩展, 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 已知且 rt 的函数 .

    我让你自己弄清楚 TypeFamilies . 如今这是一种更受欢迎的解决方案 . FunctionalDependencies 是一个较旧的扩展,它很容易理解,但在某些极端情况下会导致并发症 . 不要担心它们,在你看到之前你将成为Haskell的主人 .

相关问题