Search code examples
haskellrecords

Dynamic field lookup with records in Haskell


I'm wondering if it's possible to get all fields of a record in Haskell that end with a certain name. For example

data Record = Record {
    field       :: String
    field2_ids  :: Maybe [Int]
    field3_ids  :: Maybe [Int]
}

In this case I would like to get a list of fields ending with "ids". I don't know their names. I only know that they end with "ids" What I need is both the field name and the value it contains. So I guess this would be a list of maps

[{field2_ids = Maybe [Int]}, {fields3_ids = Maybe [Int]}...]

or even a list of tuples

[("field2_ids", Maybe [Int])...]

By the way, in my case the fields I'm extracting will always have the type of Maybe [Int].

Is this possible? I suspect that it's not possibly with vanilla record syntax but is this maybe something that could be achieved with lenses?

UPDATE

I understand my question is causing some confusion in terms of what I'm actually trying to do. So I will explain

I'm using a microservice pattern with Services. Each Service is tied to a single data model. For example a Blog Service would contain a single Blog model. But the Blog Service can have all kinds of relations. For example it can have a relation to a Category Service. It can also have a relation to a Tag Service. Since there is a possibility of having more than one relation with another Service I have the type of a Maybe [Int] since I could be posting a Blog with Just [Int] or Nothing, no relations at all. Each Service handles its relations by registering them in a Relation table.

So to create a new Blog Post I need a data structure like this one in Servant

data BlogPostRequest = BlogPostRequest {
    title :: String,
    published :: DateTime,
    public :: Bool,
    category_ids :: Maybe [Int],
    tag_ids :: Maybe [Int]
}

The endpoint will take all the fields related to the Blog model and store it as a new Blog instance. It will then take all the relations if present in category_ids and tag_ids and store it in the Relation table.

My only concern about this, using the traditional record syntax is that if I have multiple relations, the code will get very bloated. Services are generated from config files. So yes I do actually know all the names of the fields from start. I'm sorry my statement about this before was very confusing. My point was that if I could pull the fields out of the record just by knowing that their names end with _ids I could reduce a lot of the code.

This would be the vanilla record syntax approach. Imagine that storeRelation is a method which takes a String and a Maybe [Int] and handles storing the relation accordingly

createNewBlogPost post = 
    storeRelation "category" (category_ids post)
    storeRelation "tag"      (tag_ids post)
    -- continue with rest of relations

This approach might not be so bad in the end. I would just add a new line for each relation. I was just wondering if there was a straight forward way to extract fields from records so that I could have the function like this

createNewBlogPost post = 
    storRelation $ extractRelations post

where storeRelation now takes a list of tuples and extractRelations is a function which extracts the fields ending with _ids


Solution

  • Given that you actually do know all of the field names, and they are all of the same type, it should be quite a small amount of work to simply write each of the field names once, and much simpler than writing a big generic Template Haskell solution that would work for any data type.

    A simple example:

    idGetters :: [(String, Record -> Maybe [Int])]
    idGetters = [("field2_ids", field2_ids), 
                 ("field3_ids", field3_ids)]
    
    ids :: Record -> [(String, Maybe [Int])]
    ids r = fmap (fmap ($ r)) idGetters
    

    It looks a bit ugly, but that's simply the best way to work with the data structure you're presupposing.