简体   繁体   English

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

[英]Equivalent to C# generic interface in Haskell

I'm trying to convert some simple C# code to Haskell. 我正在尝试将一些简单的C#代码转换为Haskell。 So say I've got a simple immutable "database" type that is just a record with various list fields. 所以说我有一个简单的不可变“数据库”类型,它只是一个包含各种列表字段的记录。 So, say 所以,说吧

data Person = Person { }
data Book = Book { }
data Database = Database { employees :: [Person], books :: [Book], customers :: [Person] }

Now I want to create a typeclass that represents a "view", or essentially a "table" of that DB. 现在我想创建一个类型类,表示“视图”,或者本质上是该数据库的“表”。

class Table r t where -- r is the record type (e.g. Person or Book)
  getRecords :: t -> Database -> [r]
  setRecords :: t -> [r] -> Database -> Database

Then I can create instances that represent each of those tables: 然后我可以创建代表每个表的实例:

data ET = EmployeeTable
instance (Table Person) ET where
  getRecords t db = employees db
  setRecords t records db = Database records (books db) (customers db)

This is what I have, and it works, but only if {-# LANGUAGE MultiParamTypeClasses #-} is included. 这就是我所拥有的,并且它可以工作,但{-# LANGUAGE MultiParamTypeClasses #-}是包含{-# LANGUAGE MultiParamTypeClasses #-} Otherwise the definition of the Table typeclass fails. 否则,表类型类的定义失败。

Not a big deal in itself: it compiles and works, but a quick read on MultiParamTypeClasses alludes to potential complications down the line (I haven't taken the time to fully grok them yet). 本身并不是什么大不了的事:它可以编译和工作,但是快速阅读MultiParamTypeClasses可以解决潜在的复杂问题(我还没有花时间完全搞定它们)。

The weird thing for me though is that this is very straightforward in C#. 对我来说奇怪的是,这在C#中非常简单。 Assuming simple immutable definitions of the record/DB class, it's simple to define the interface, and then the implementations follow without issue. 假设记录/数据库类的简单不可变定义,定义接口很简单,然后实现遵循没有问题。

interface ITable<TRecord> {
    TRecord[] GetRecords(Database db);
    Database SetRecords(TRecord[] records, Database db);
}

So really that's the essence of this question. 真的,这就是这个问题的本质。 Is there a more idiomatic way to translate the functionality accorded by the above ITable<TRecord> interface from C# to Haskell? 是否有更惯用的方法将上述ITable<TRecord>接口所赋予的功能从C#转换为Haskell? My understanding is that C# interfaces are closest to Haskell typeclasses, so that's what I'm trying to do. 我的理解是C#接口最接近Haskell类型类,所以这就是我想要做的。 But I find it suprising that something as simple as a generic interface requires a language extension in the highly-touted type system in Haskell. 但我觉得很奇怪,像通用接口这样简单的东西需要在Haskell中高度吹捧的类型系统中进行语言扩展。

(NB why do I want to do this? The above is a bit simplified for brevity's sake, but in general, if I make the fleshed-out Person and Book instances of a Record typeclass that just has getId , then I can support CRUD functions for the whole database very generically: I only have to define these functions once for the Table typeclass, and they'll apply to all tables in the DB automatically. Here's the full code a sample usage of it at the bottom, and its C# equivalent. https://gist.github.com/daxfohl/a785d1ff72b921d7e90b70f625191a1c . Note in Haskell deleteRecord doesn't compile either since the type of r cannot be deduced, whereas in C# it compiles fine. This adds to my thought that maybe MultiParamTypeClasses is not the right approach. But if not, then what is ?) (NB 为什么我要这样做?为简洁起见,上面的内容有点简化,但一般来说,如果我创建了一个只有getIdRecord类型类的充实PersonBook实例,那么我可以支持CRUD函数对于整个数据库非常一般:我只需要为Table类型类定义一次这些函数,它们将自动应用于DB中的所有表。这里是完整代码底部的示例用法,以及它的C#等价物.https: deleteRecord 。注意Haskell中的deleteRecord不能编译,因为r的类型无法推断,而在C#中它编译得很好。这增加了我的想法,也许MultiParamTypeClasses不是正确的做法。但如果不是,那又什么?)

Update 更新

Okay it sounds from the comments like MultiParamTypeClasses is fine. 好吧,它听起来像MultiParamTypeClasses这样的评论很好。 So now my remaining question is how to fix the linked gist such that deleteRecord will compile? 所以现在我剩下的问题是如何修复链接的要点,以便deleteRecord将编译?

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

This is a problem. 这是个问题。 There's an r before the => but no r after the => . 这里有一个r的前=>但没有r=> Given a function call like deleteRecord bookTable 1 db , Haskell has no idea which r you are talking about. 给定一个函数调用像deleteRecord bookTable 1 db ,哈斯克尔不知道哪些r你在说什么。 Though r should be completely determined by t , Haskell has no way of knowing that. 虽然r应该完全由t决定,但Haskell无法知道这一点。 Indeed, no one disallows these instance definitions: 实际上,没有人不允许这些实例定义:

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

There is nothing in the "table" types that could prevent this. “表”类型中没有任何内容可以阻止这种情况。 They are just empty tags without any real data. 它们只是没有任何实际数据的空标签。

The fact that there is only one instance of interest in your module is irrelevant, Haskell cannot guarantee anything about other modules. 事实上,您的模块中只有一个感兴趣的实例是无关紧要的,Haskell无法保证其他模块的任何内容。

So what are your options here? 那你有什么选择呢?

  1. Get rid of table types altogether. 完全摆脱表格类型。 Record types could be enough. 记录类型就足够了。
  2. Use another Haskell extension, FunctionalDependencies . 使用另一个Haskell扩展, FunctionalDependencies
  3. Use yet another Haskell extension, TypeFamilies . 使用另一个Haskell扩展, TypeFamilies

The last two extensions allow for creation of generic containers among other things, which is what your database tables essentially are. 最后两个扩展允许创建通用容器,这是数据库表本质上的。

Here's how Table type class would look with the first extension: 以下是Table类型在第一个扩展名中的外观:

class Record r => Table r t | t -> r where ...

The notation t -> r means that r is completely determined by t (IOW r functionally depends on t ). 符号t -> r表示r完全由t确定(IOW r 功能上取决于 t )。 Once Haskell sees an instance Table Foo Bar , it knows there can be no instance Table Qux Bar (the compiler will signal an error should it see a conflicting definition). 一旦Haskell看到一个实例Table Foo Bar ,就会知道没有实例Table Qux Bar (编译器会在看到相互冲突的定义时发出错误信号)。 This way deleteRecord is well-formed. 这样deleteRecord就是格式良好的。 r is not in the signature but it's OK: t is known and r is a function of t . r不在签名中但是没关系: t是已知的并且rt的函数。

I let you figure out TypeFamilies by yourself. 我让你自己弄清楚TypeFamilies It's a more popular solution these days. 如今这是一种更受欢迎的解决方案。 FunctionalDependencies is an older extension, it is simple to understand but can lead to complications in some corner cases. FunctionalDependencies是一个较旧的扩展,它易于理解,但在某些极端情况下会导致复杂化。 Don't worry about them, you will be a master of Haskell before you see any. 不要担心它们,在你看到之前你将成为Haskell的主人。

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

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