Skip to content

Request: relax `HasField` requirements

I would like to provide persistent users with a HasField instance on Entity that would allow them to leverage the SymbolToField class we auto-generate to have easy field access on Entity Foo table representations.

This works nicely with esqueleto's database representation, SqlExpr:

-- |  This instance allows you to use @record.field@ notation with GHC 9.2's
-- @OverloadedRecordDot@ extension.
--
-- Example:
--
-- @
-- -- persistent model:
-- BlogPost
--     authorId     PersonId
--     title        Text
--
-- -- query:
-- 'select' $ do
--     bp <- 'from' $ 'table' \@BlogPost
--     pure $ bp.title
-- @
--
-- This is exactly equivalent to the following:
--
-- @
-- blogPost :: SqlExpr (Entity BlogPost)
--
-- blogPost ^. BlogPostTitle
-- blogPost ^. #title
-- blogPost.title
-- @
-- There's another instance defined on @'SqlExpr' ('Entity' ('Maybe' rec))@,
-- which allows you to project from a @LEFT JOIN@ed entity.
--
-- @since 3.5.4.0
instance
    (PersistEntity rec, PersistField typ, SymbolToField sym rec typ)
  =>
    HasField sym (SqlExpr (Entity rec)) (SqlExpr (Value typ))
  where
    getField expr = expr ^. symbolToField @sym

As it happens, this only works because SqlExpr is not already a record. When I try to define this on Entity, which is defined as Entity { entityKey :: Key rec, entityVal :: rec }, I get this error:

instance
    ( SymbolToField sym ent typ
    , PersistEntity ent
    )
  =>
    HasField sym (Entity ent) typ
  where
    getField ent =
        view (persistFieldLens (symbolToField @sym @ent @typ)) ent
/home/matt/Projects/persistent/persistent/Database/Persist/Class/PersistEntity.hs:249:5: error:
    • Illegal instance declaration for ‘HasField sym (Entity ent) typ’
        Entity has fields
    • In the instance declaration for ‘HasField sym (Entity ent) typ’
    |
249 |     HasField sym (Entity ent) typ
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The obvious conflict is if SymbolToField "entityKey" rec typ existed - then we'd have a duplicate/overlapping instance, and that would be bad. Otherwise, it seems like the functional dependencies on SymbolToField should render this pretty safe.

class SymbolToField (sym :: Symbol) rec typ | sym rec -> typ where
    symbolToField :: EntityField rec typ

I can pretty easily work-around this in the persistent case by just generating the instances via TemplateHaskell.

instance HasField "userName" (Entity User) String where
    getField = view (persistFieldLens UserName)

But, if these are orphans, it'd be pretty annoying to disable the orphan warning flag for each module that defines persistent entities and opts in to this feature.

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information