繁体   English   中英

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

[英]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 为什么我要这样做?为简洁起见,上面的内容有点简化,但一般来说,如果我创建了一个只有getIdRecord类型类的充实PersonBook实例,那么我可以支持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无法保证其他模块的任何内容。

那你有什么选择呢?

  1. 完全摆脱表格类型。 记录类型就足够了。
  2. 使用另一个Haskell扩展, FunctionalDependencies
  3. 使用另一个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的主人。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM