Search code examples
databasehaskellyesodtemplate-haskellhamlet

Can I Use Template Haskell to Generate Hamlet Code?


I have come across an interesting problem that I thought might be a pertinent place for Template Haskell. I'm working on a web front-end to a database using yesod and yesod-persistant. I am generating my database types using mkPerist function and the persistLowerCase quasi-quoter. My problem is, I need a way to edit fields of the database but writing the hamlet code for six different pages for each of the columns seems incredibly repetitive. I figured I could use Template Haskell to automatically generating the text fields and checkboxes for editing that column of the database given the type. Ideally I would just pass the name of the type to the Template Haskell function and then TH would take care of generating all of the Hamlet for the page. My question is, can I use Template Haskell in this case? Is it the best solution? Particularly, can Template Haskell generate code for other quasi-quoters? Particularly Hamlet? Here is a link to my project as of now: https://github.com/ProspectRidgeTech/PRADatabase Thanks in advance! (PS. Let me know if there is a better way to approach this problem and if you have any suggested edits to my question.)


Solution

  • To answer your question : Yes you can, however I wouldn't recommend it. A quasi quoter is just a function which take a string and generate some code, so you when you see

    [hamlet|blah blah|]
    

    You could replace it with (or equivalent)

    $(hamlet "blah blah")
    

    So nothing stops you in TH to generate a string an call hamlet to it. However, one of the point of TH is type safety. Generating a string to then parse kind of defeat the object of it. Also, this 2 steps code generation will probably be hard to debug.

    Anyway, if your problem is to generate table for Persistent entities, I don't think you need TH at all and just use Persistent fields information. I had a similar problem and writen some code which generate an Html table for a list of entities. It shouldn't be hard to modify it do input.

    entitiesToTable :: PersistEntity a => (FieldDef -> Text) -> [Entity a] -> Html
    entitiesToTable getColumn entities = do
      let eDef = entityDef (map entityVal entities)
      [shamlet|
    <table.table.table-bordered.table-striped class="#{unHaskellName $ entityHaskell eDef}">
      <tr>
        <th> Id
        $forall field <- entityFields eDef
          <th> #{getColumn field}
      $forall Entity eit entity  <- entities
        <tr>
          <td.id> #{renderPersistValue $ toPersistValue eit}
          $forall (pfield, fieldDef) <- zip (toPersistFields entity) (entityFields eDef)
            <td class="#{getHaskellName fieldDef}" > #{renderPersistValue $ toPersistValue pfield}
    |]
    

    Writing the code to process the form and update the database might be more tricky and need TH, however there is no Hamlet involved in this step.