I'm trying to convert some simple C# code to 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. 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).
The weird thing for me though is that this is very straightforward in 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? My understanding is that C# interfaces are closest to Haskell typeclasses, so that's what I'm trying to do. 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.
(N.B. 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?)
Update
Okay it sounds from the comments like MultiParamTypeClasses is fine. So now my remaining question is how to fix the linked gist such that deleteRecord will compile?
MultiParamTypeClasses, then clear that out of your head. This is a pretty standard extension to enable. The reason why these extensions exist is to allow different Haskell compilers to implement new behavior beyond the Haskell Standard, which can eventually become standard. I'll note that the "dilemmas" linked to by prime.haskell.org/wiki/MultiParamTypeClasses is an article last modified 11 years ago. Since then it's become easier to work with.getIdpart multiple Times. This is somewhat true without further techniques, and it comes down to how Haskell does name resolution. You could look at the lens library, which solves a lot of these problems at the cost of several compiler extensions.