Reputation: 15872
Here's the tutorial I'm working from.
He has an example, tupleReplicate
, which returns a function that takes a value and replicates it:
tupleReplicate :: Int -> Q Exp
tupleReplicate n = do id <- newName "x"
return $ LamE (VarP id)
(TupE $ replicate n $ VarE id)
So VarE id
returns an expression that can then be used with replicate
? My question is, how would this work if id
was a list? I want to do something like:
let vals = mkName "vals"
LamE (ListP vals) (map reverse $ ListE vals)
except that doesn't work because ListE
returns Exp
, not [Exp]
.
More generally I want to write a function that takes a list and applies a function to it, in TemplateHaskell.
There's some example code here, and I'm trying to write a function like
makeModel (id_ : name_ : []) = Person (fromSql id_) (fromSql name_)
Upvotes: 5
Views: 217
Reputation: 32455
First, let's turn on some extensions:
{-# LANGUAGE FlexibleInstances, TemplateHaskell #-}
import Language.Haskell.TH
Now I'll fake some data types and classes to keep the interaction with the Real World low:
data Person = Person Int String deriving Show
class SQL a where
fromSql :: String -> a
instance SQL Int where fromSql = read
instance SQL String where fromSql = id -- This is why I needed FlexibleInstances
OK, now we need to decide what code we want to generate. Sticking closely to your example, we could define makeModel as a lambda expression (translation underneath):
LamE [ListP [VarP id,VarP name]] (AppE (AppE (ConE Person) (AppE (VarE fromSql) (VarE id))) (AppE (VarE fromSql) (VarE name)))
\ [ id, name ] -> ( ( Person ( fromSql id )) ( fromSql name ))
\ [ id, name ] -> Person $ fromSql id $ fromSql name
(I don't speak fluent Exp
, I did runQ [| \[id,name] -> Person (fromSql id) (fromSql name) |]
in ghci!)
I've chosen to use strings do define the identifiers id
and name
, because you could read that from the table, but you could just as well generate identifiers called field_1
etc.
makeMakeModel qFieldNames qMapFunction qConstructor = -- ["id","name"] 'fromSql 'Person
LamE [ListP (map VarP qFieldNames)] -- \ [id,name]
$ foldl AppE (ConE qConstructor) -- Person
[AppE (VarE qMapFunction) (VarE name)|name <- qFieldNames]
-- $ id $ name
makeModel fieldNames mapFunction constructor = do
names <- mapM newName fieldNames
return $ makeMakeModel names mapFunction constructor
In action in ghci -XTemplateHaskell
:
*Main> runQ $ makeModel ["id","name"] 'fromSql 'Person
LamE [ListP [VarP id_0,VarP name_1]] (AppE (AppE (ConE Main.Person) (AppE (VarE Main.fromSql) (VarE id_0))) (AppE (VarE Main.fromSql) (VarE name_1)))
*Main> $(makeModel ["id","name"] 'fromSql 'Person) ["1234","James"]
Person 1234 "James"
Notice how the identifiers we made with newName
have got serial numbers to make them unique, whereas the ones we passed in with a dash in front, 'fromSql
and 'Person
are preserved as their actual definitions.
If you'd rather not use a lambda expression, you can use
runQ [d| makeModel [id,name] = Person (fromSql id) (fromSql name) |]
as your starting point - [d| ... |]
is for function definitions.
Upvotes: 2