Handling multiple data types in compiled Heist

22 views
Skip to first unread message

sal

unread,
Mar 18, 2016, 10:58:36 AM3/18/16
to Snap Framework
This is more of a design question about how to handle multiple data types. Let us say we have n different data types (each data type corresponds one-to-one to a heist template) - a simplified example below for two different transaction types - let Trans be the data constructor (simplified here - it might need to use record field syntax for JSON handling):
Trans (Int Int)
Trans (Int Text)

We want to map each transaction type to the corresponding Builder in Heist (which generates the appropriate XML that we send over as SOAP request for that transaction). What is a good way to do this? May be use indexed data family, or use some kind of dynamic map? I am trying to avoid creating n different types, and too much boilerplate. 

I am pretty sure I haven't thought it through yet. So, if you spot anything wrong in my question, please feel free to correct me, or propose an alternative solution to solve this. Basically, the problem is that we have n different data formats, and n Heist templates corresponding to each of these. What is good way to handle this without writing too much boiler plate? Any toy example code will also be very helpful!

MightyByte

unread,
Mar 19, 2016, 7:20:35 AM3/19/16
to snap_fr...@googlegroups.com
Hi there, I'm not quite sure I understand your question correctly.
First of all, I have a minor question to make sure we're on the same
page. Instead of this:

Trans (Int Int)
Trans (Int Text)

...did you actually mean this?

Trans Int Int
Trans Int Text

I think what you put won't actually build because Int has kind *, not
kind * -> *. The rest of my answer assumes you meant the latter.

The first thing that comes to mind for your question is to use a type
class. I'm thinking something like this:

class TransHasTemplate a where
transactionTemplateName :: a -> ByteString

Then you can make instances do your work.

instance TransHasTemplate (Trans Int Int) where
transactionTemplateName _ = "intIntTemplate"

instance TransHasTemplate (Trans Int Text) where
transactionTemplateName _ = "intTextTemplate"

Alternatively you could use a different formulation:

transactionTemplate :: a -> m Builder

This would have the type class function return the result of the
renderTemplate call rather than its input. The two formulations
accomplish pretty much the same thing, although the latter formulation
has slightly more complicated types and is maybe slightly harder to
think about.

sal

unread,
Mar 19, 2016, 10:26:40 AM3/19/16
to Snap Framework
Thank you. I did make the mistake in data constructor. You got it right.

Typeclass approach would work, especially:
transactionTemplate :: a -> m Builder

I am actually looking deeper into whether something like Aeson deriveGeneric approach can help here. The reason is because building splices seems to be something that could be automatically derived using data types. For example, let us say we have an authentication request - what I am looking into is how to do automatic derivation of splice using the data type (assuming that data type itself encodes the splice pattern - please see the comment under `authSpliceH
` function below). "C" below is import alias for "Heist.Compiled".

-- This is our authentication request
 
data
AuthAPI = AuthAPI {

    _aapi_user
:: Text

   
,_aapi_pass :: Text

 
} deriving




-- Auth Builder

genAuthBuilder
:: FilePath -> B.ByteString -> IO (StateT AuthAPI IO Builder)

genAuthBuilder baseDir templName
= do

  hs
<- load baseDir ("auth" ## authSplice)
 
return $ fst . fromJust $ C.renderTemplate hs templName -- C is alias for Heist.Compiled




-- This part could be automated by reading from AuthAPI data type using some kind of generic or TH mechanism?

authSpliceH
:: Monad n => RuntimeSplice n AuthAPI -> C.Splice n

authSpliceH
= C.withSplices C.runChildren $ do

   
"user" ## (C.pureSplice . C.textSplice $ _aapi_user) -- first argument comes from: Prelude.drop 6 "_aapi_user"

   
"pass" ##  (C.pureSplice . C.textSplice $ _aapi_pass) -- first argument comes from: Prelude.drop 6 "_aapi_pass"


authSplice
:: C.Splice (StateT AuthAPI IO)
authSplice
= authSpliceH (lift get)


This is just a simplified example - we can safely assume that we take all fields of a data constructor (AuthAPI in the example above), and apply some kind of processing to come up with splice pattern ("user" and "pass" above - I have modeled it after Aeson deriveJSON). I could apply something like that to "Trans a" type where we have like 30 different data constructors (30 different transactions) for "a". Making splice building an automatic derivation would be a big help since all we need to do is to define the data constructor, and let the splice be derived automatically like in Aeson, instead of manually writing the code for each data constructor.


Has something like this been done before? If so, it will be very helpful to have example code. Meanwhile, I will keep reading Aeson source code to figure out how to do automatic derivation.

sal

unread,
Mar 19, 2016, 11:16:55 AM3/19/16
to Snap Framework
Some corrections in the code that I sent out:

AuthAPI with missing deriving clause added (got cut-off somehow in previous post):

data AuthAPI = AuthAPI {
    _aapi_user
:: Text
   
,_aapi_pass :: Text

 
} deriving (Read, Show)


Definition of `load` function:
load :: MonadIO n => FilePath -> Splices (Splice n) -> IO (HeistState n)
load baseDir splices
= do
    tmap
<- runEitherT $ do
        let sc
= mempty & scLoadTimeSplices .~ defaultLoadTimeSplices
                       
& scCompiledSplices .~ splices
                       
& scTemplateLocations .~ [loadTemplates baseDir]
        initHeist $ emptyHeistConfig
& hcNamespace .~ ""
                                     
& hcErrorNotBound .~ False
                                     
& hcSpliceConfig .~ sc
    either
(error . Prelude.concat) return tmap


`B.ByteString` is from `Data.ByteString` which `B` is alias for.

`StateT` is `Control.Monad.Trans.State.Strict.StateT`.

MightyByte

unread,
Mar 20, 2016, 1:03:13 PM3/20/16
to snap_fr...@googlegroups.com
Yes, I think you're right that it might be possible to automatically
generate splices based on data types. I'm not aware of any attempts
to do it yet. It would be great if you could come up with something!
> --
>
> ---
> You received this message because you are subscribed to the Google Groups
> "Snap Framework" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to snap_framewor...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

sal

unread,
Mar 23, 2016, 9:37:44 AM3/23/16
to Snap Framework
Well, I have a generic heist builder now :) It works for very simple data structures which is good enough (along the lines I laid out in previous post). Here is the code on how to do this - I used Template Haskell for now but will probably investigate generics or generics-sop approach in next few days. I have few missing gaps in my knowledge of generic programming. So, I resorted to template haskell for now. Here is the gist - it will be nice if we could add to Heist documentation after some cleaning up - happy to help:

For those who are too busy to look at gist, here is the main function - deriveHeistBuilder does the code generation - I have made one of the record fields "String" to show how we can handle different types, not just Text:
{-# LANGUAGE TemplateHaskell #-}

 

module Main where
import Derive
import Data.Text (Text)

data
AuthAPI = AuthAPI { _autha_uname :: String, _autha_pass :: Text} deriving (Show)  

deriveHeistBuilder
(Prelude.drop 7) ''AuthAPI


main
= do
  authBuilder
<- genSpliceBuilder "." "auth"
  req
<- genReqH (authBuilder) (AuthAPI { _autha_uname = "JohnDoe", _autha_pass = "whatdoyouthink"})
 
print req



auth.tpl:

<API_root><authapi><login><user><uname/></user><password><pass/></password></login></authapi></API_root>

Output of main:
*Main> main
"<API_root><login><user>JohnDoe</user><password>whatdoyouthink</password></login></API_root>"


MightyByte

unread,
Mar 23, 2016, 10:11:10 AM3/23/16
to snap_fr...@googlegroups.com
Very cool! I'm definitely interested in incorporating this into
Heist. Since we're pushing towards the 1.0 release I don't think it
makes sense to release it in the 0.14 series. I'll be happy to work
with you to put together a pull request adding this to Heist. I would
want to make sure we have good test coverage for it as well as have
good documentation for how to use it. Is that something you would be
interested in working on?

sal

unread,
Mar 23, 2016, 10:22:43 AM3/23/16
to Snap Framework
Yep, absolutely happy to help with this for next release, including test coverage and documentation. I plan to use this feature heavily in a production code in next few weeks, and so, will be quite involved with refining this library.

MightyByte

unread,
Mar 23, 2016, 11:29:38 AM3/23/16
to snap_fr...@googlegroups.com
Fantastic! Thanks for all the hard work.
Reply all
Reply to author
Forward
0 new messages