Search code examples
haskellpolymorphismdto

Generic type for data transfer records in Haskell


I want to exchange data with some remote system (can be via HTTP, a database, files, etc.), and therefore create data transfer objects (DTOs) - basically, records that can be serialised and deserialised easily. In a second step, I want to map these DTOs to my domain objects.

All DTOs have an ID field (e.g., a technical primary key, a message identifier, or a file name). Because this is a general pattern, I try to capture it in a general type. This is my first attempt:

data DtoShell = DtoShell
   { id :: UUID
   , dto :: Dto}

data Dto
   = MyDto1 ConcreteDto1
   | MyDto2 ConcreteDto2
   | ...

In this way, functions that are concerned with handling arbitrary DTOs, and therefore only need the ID, can deal with the DtoShellonly but do not need to care about the actual data inside. Functions that are particular to a specific DTO can work with that. In between, there must be some "dispatcher" function that pattern-matches on the concrete type and selects the appropriate function - for example, to map a concrete DTO to its corresponding domain type.

However, this scheme is hard to extend - if there ever is a new ConcreteDto3, I need to change the above code, and I need to change the "dispatcher" function.

I do see that this seems to be a representation of some more fundamental problem (the expression problem? I am not a CS major...). In my search, I came across potential solutions: Phantom types, existential types, and, foremost, typeclasses. But I am not yet proficient enough in Haskell and general abstract CS to value the pros and cons of each approach. Thus, I have two questions:

  1. Is there a general, accepted practice or pattern how to model such "identifiable records"? E.g., when accessing relational databases.
  2. Is there a single resource (book, paper, tutorial) that compares the different approaches to data abstraction?

Solution

  • Why not just create a parametrically polymorphic envelope?

    data Envelope a = Envelope { eid :: UUID, edata :: a } deriving (Eq, Show, Functor)
    

    This is basically just a specialised pair (two-tuple), so you make it an instance of all the type classes that (,) is an instance of. Here I've made it a Functor instance by using the DeriveFunctor language extension.