Skip to content

GitLab

  • Menu
Projects Groups Snippets
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
  • Sign in / Register
  • GHC GHC
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
    • Locked Files
  • Issues 4,862
    • Issues 4,862
    • List
    • Boards
    • Service Desk
    • Milestones
    • Iterations
  • Merge requests 455
    • Merge requests 455
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
    • Test Cases
  • Deployments
    • Deployments
    • Releases
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Code review
    • Insights
    • Issue
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • Glasgow Haskell Compiler
  • GHCGHC
  • Issues
  • #21369
Closed
Open
Created Apr 08, 2022 by parsonsmatt@trac-parsonsmatt

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
Assignee
Assign to
Time tracking