Haxl is an amazing library, but one of the major pain points I find is caused by the fact that each sort of request to the data source requires its own constructor in the Request GADT. For example, taking the example from the tutorial:
data BlogRequest a where
FetchPosts :: BlogRequest [PostId]
FetchPostContent :: PostId -> BlogRequest PostContent
Then each of these constructors are pattern matched on and processed separately in the match function of the DataSource instance. This style results in a lot of boilerplate for a non-trivial application. Take for example an application using a relational database, where each table has a primary key. There may be many hundreds of tables so I don't want to define a constructor for each table (let alone on all the possible joins across tables...). What I really want is something like:
data DBRequest a where
RequestById :: PersistEntity a => Key a -> DBRequest (Maybe a)
I'm using persistent to create types from my tables, but that isn't a critical detail -- I just want to use a single constructor for multiple possible return types.
The problem comes when trying to write the fetch function. The usual procedure with Haxl is to pattern match on the constructor to separate out the various types of BlockedFetch requests, which in the above example would correspond to something like this:
resVars :: [ResultVar (Maybe a)]
args :: [Key a]
(psArgs, psResVars) = unzip
[(key, r) | BlockedFetch (RequestById key) r <- blockedFetches]
...then I would (somehow) group the arguments by their key type, and dispatch a SQL query for each group. But that approach won't because here may be requests for multiple PersistentEntity
types (i.e. database tables), each of which is a different type a
, so building the list is impossible. I've thought of using an existentially quantified type to get around this issue (something like SomeSing in the singletons library), but then I see no way to group the requests as required without pattern matching on every possible table/type.
Is there any way to achieve this sort of code reuse?
PersistentEntity a => Key a -> DBRequest (Maybe a)
? (2) “…but that won't work if there's only a single constructor.” Why not? – Alexis Kingfetch
. In short, the challenge is batching the BlockedFetches by the typea
(which corresponds to a database table) so I can make a SQL query for each. – RichardW