First release of derive-has-field. Derive HasField instances for records

We have been using OverloadedRecordDot at work and I really like it. Persistent has this feature where it will derive HasField instances for each of the field names. I.e.

BankAccount
  accountNumber String

Allows you to write bankAccount.accountNumber instead of bankAccount.bankAccountAccountNumber. I want this for all of my data types! Check out
derive-has-field: Derive HasField instances with Template Haskell and the source code GitHub - chiroptical/derive-has-field

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TemplateHaskell #-}

import DeriveHasField

data BankAccount =
  BankAccount
    { bankAccountAccountNumber :: String
    }

deriveHasFieldWith (dropPrefix "bankAccount") ''BankAccount

Hope you enjoy!

9 Likes

Have you considered dropping the prefixed fields and go the -XDuplicateRecordFields route (perhaps in combination with -XNoFieldSelectors?)

1 Like

Yes. IIRC, the issue here is related to record updates.

Honestly, I am excited for the day where this all “just works” and I don’t have to think about the downsides. For now, this is a solution one could choose.

1 Like

I’ve experienced that record update pain (GHC complains ambiguous field updates all the time).

Sometimes I resort to the import alias trick to avoid the ambiguities, sometimes to generic-lens.

The combination of lens, generic-lens, and DuplicateRecordFields works great for us.

4 Likes

After reading the docs for DuplicateRecordFields it seems that explicitly performing construction / deconstruction helps with updates, because

Uses of fields that are always unambiguous because they mention the constructor, including construction and pattern-matching, may freely use duplicated field names.

So, if we have

data Foo = Foo {
    aaa :: Int,
    bbb :: Bool
}

data Bar = Bar {
    aaa :: Int,
    bbb :: Bool
}

we can write (with a little help from RecordWildcards)

boo :: Foo -> Bar -> Bar
boo foo Bar {aaa,..} = Bar { aaa = foo.aaa, .. }

even if this would be ambiguous:

boo' :: Foo -> Bar -> Bar
boo' foo bar = bar { aaa = foo.aaa }

Not a perfect solution, but might come in handy in some cases.