Haskell pattern code generation is a very complicated topic. However, if you master the art of TH, you can use this technique to generate the code that you are looking for.
Note that the following code will only work for types datawith only one constructor (for example, not data Foo = A String Int | B String Int, which has two Aand constructors B), because you did not say how this should be handled in the code.
Template Haskell, , sqlTo<nameofdatatype>. :
module THTest where
import Control.Monad (replicateM)
-- Import Template Haskell
import Language.Haskell.TH
-- ...and a representation of Haskell syntax
import Language.Haskell.TH.Syntax
-- A function that takes the name of a data type and generates a list of
-- (function) declarations (of length 1).
makeSqlDeserializer :: Name -> Q [Dec]
makeSqlDeserializer name = do
-- Look up some information about the name. This gets information about what
-- the name represents.
info <- reify name
case info of
-- Is the name a type constructor (TyConI) of a data type (DataD), with
-- only one normal constructor (NormalC)? Then, carry on.
-- dataName is the name of the type, constrName of the constructor, and
-- the paramTypes are the constructor parameter types.
-- So, if we have `data A = B String Int`, we get
-- dataName = A, constrName = B, paramTypes = [String, Int]
TyConI (DataD _ dataName _ [NormalC constrName paramTypes] _) -> do
-- If the dataName has a module name (Foo.Bar.Bla), only return the data
-- name (Bla)
let dataBaseName = nameBase dataName
-- Make a function name like "sqlToBla"
let funcName = mkName $ "sqlTo" ++ dataBaseName
-- Also access the "fromSql" function which we need below.
let fromSqlName = mkName "Database.HDBC.fromSql"
-- Count how many params our data constructor takes.
let numParams = length paramTypes
-- Create numParams new names, which are variable names with random
-- names.
-- This could create names like [param1, param2, param3] for example,
-- but typically they will look like
-- [param[aV2], param[aV3], param[aV4]]
paramNames <- replicateM numParams $ newName "param"
-- The patterns are what on the left of the `=` in the function, e.g.
-- sqlToBla >>>[param1, param2, param3]<<< = ...
-- We make a list pattern here which matches a list of length numParams
let patterns = [ListP $ map VarP paramNames]
-- The constructor params are the params that are sent to the
-- constructor:
-- ... = Bla >>>(fromSql param1) (fromSql param2) (fromSql param3)<<<
let constrParams = map (AppE (VarE fromSqlName) . VarE) paramNames
-- Make a body where we simply apply the constructor to the params
-- ... = >>>Bla (fromSql param1) (fromSql param2) (fromSql param3)<<<
let body = NormalB (foldl AppE (ConE constrName) constrParams)
-- Return a new function declaration that does what we want.
-- It has only one clause with the patterns that are specified above.
-- sqlToBla [param1, param2, param3] =
-- Bla (fromSql param1) (fromSql param2) (fromSql param3)
return [FunD funcName [Clause patterns body []]]
( LANGUAGE , Template Haskell):
{-
-- The module that defines makeSqlDeserializer (must be in a different module!)
import THTest
-- Also import the fromSql function which is needed by the generated function.
import Database.HDBC
-- Declare the data type
data Bla = Bla String Int deriving (Show)
-- Generate the sqlToBla function
makeSqlDeserializer ''Bla
, -ddump-splices GHC . :
test.hs:1:1: Splicing declarations
makeSqlDeserializer 'Bla
test.hs:7:1-25
sqlToBla [param[aV2], param[aV3]]
= Bla (Database.HDBC.fromSql param[aV2]) (Database.HDBC.fromSql param[aV3])