How to Write a Type Signature for a Generic `fetchGenericJson` Function

I’m trying to create a generic function to fetch records by their IDs. Something like:

fetchGenericJson recordIds = do
    records <- query @record
      |> filterWhereIn (#id, recordIds)
      |> fetch

    map toJSON records

I want to call it like so:

fetchGenericJson @News recordIds

However, I’m not sure how to write the type signature for fetchGeneric so that it works generically with any record type (e.g., News, User, etc.) and enforces the necessary type constraints.

What would be the correct type signature for fetchGeneric?

1 Like

I’m trying to base the signature on ihp/IHP/Fetch.hs at 49d9e233cf003d6cc18515297d3861a0ce9a9d64 · digitallyinduced/ihp · GitHub

fetchGenericJson :: forall table model. (Table model, KnownSymbol table, FromRow model, ?modelContext :: ModelContext, FilterPrimaryKey table, model ~ GetModelByTableName table, GetTableName model ~ table, ToJSON model) => [Id' table] -> IO Value
fetchGenericJson recordIds = do
    records <- query @model
            |> filterWhereIn (#id, recordIds)
            |> fetch


    fmap toJSON records

But it doesn’t like it

ihp      | Web/Controller/DataSyncManager.hs:83:16: error: [GHC-83865]
ihp      |     • Couldn't match type: [model]
ihp      |                      with: IO a0
ihp      |       Expected: QueryBuilder table -> IO (IO a0)
ihp      |         Actual: QueryBuilder table
ihp      |                 -> IO (FetchResult (QueryBuilder table) model)
ihp      |     • In the second argument of ‘(|>)’, namely ‘fetch’
ihp      |       In a stmt of a 'do' block:
ihp      |         records <- query @model |> filterWhereIn (#id, recordIds) |> fetch
ihp      |       In the expression:
ihp      |         do records <- query @model |> filterWhereIn (#id, recordIds)
ihp      |                         |> fetch
ihp      |            fmap toJSON records
ihp      |    |
ihp      | 83 |             |> fetch
ihp      |    |                ^^^^^

Got it to compile, but still not sure it’s right.

import Database.PostgreSQL.Simple.ToField

fetchGenericJson :: forall table model.
    ( Table model
    , KnownSymbol table
    , FromRow model
    , ?modelContext :: ModelContext
    , FilterPrimaryKey table
    , model ~ GetModelByTableName table
    , GetTableName model ~ table
    , HasField "id" model (Id' table)
    , ToField (PrimaryKey table)
    , ToJSON model
    ) => [Id' table] -> IO Value
fetchGenericJson recordIds = do
    records <- query @model
            |> filterWhereIn (#id, recordIds)
            |> fetch


    pure (toJSON records)

Next is figuring how to call this function correctly

1 Like

I wish there were some shorthand for such sequences of constraints, so one could just do something like forall table model. (?modelContext :: ModelContext, ToJSON model, model ~ IsModelOfTable table) => [Id' table] -> IO Value . But I guess that would just push the complexity elsewhere.

I had to fight with the compiler, but here’s what I got

fetchGenericJson :: forall model table.
    ( Table model
    , table ~ GetTableName model
    , model ~ GetModelByTableName table
    , KnownSymbol table
    , FromRow model
    , ?modelContext :: ModelContext
    , FilterPrimaryKey table
    , HasField "id" model (Id' table)
    , ToField (PrimaryKey table)
    , ToJSON model
    ) => [Id' table] -> IO Value
fetchGenericJson recordIds = do
    records <- query @model
            |> filterWhereIn (#id, recordIds)
            |> fetch

    pure (toJSON records)

Now I want to call it with this (where ids is [UUID])

json <- fetchGenericJson @News ids

In my code I have a Revision table that holds the recordId (the UUID), so this is working

revisions <- query @Revision |> fetch

let ids = map recordId revisions |> map packId
json <- fetchGenericJson @News ids
renderJson json
2 Likes