Во-первых, давайте обратимся на некоторых расширений:
{-# LANGUAGE FlexibleInstances, TemplateHaskell #-}
import Language.Haskell.TH
Теперь я буду подделкой некоторые типы данных и классы для поддержания взаимодействия с реальным миром:
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
Итак, нам нужно решить, какой код мы хотим сгенерировать. Придерживаясь близко к вашему примеру, мы могли бы определить makeModel как лямбда-выражения (перевод внизу):
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
(я не говорю свободно Exp
, я runQ [| \[id,name] -> Person (fromSql id) (fromSql name) |]
в GHCI!)
Я выбрал использовать строки этого определения идентификаторов id
и name
, потому что вы могли прочитать, что из-за стола, но вы могли бы точно также генерировать идентификаторы называемые field_1
и т.д.
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
В действии в 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"
Обратите внимание, как идентификаторы, которые мы сделали с newName
получили серийные номера, чтобы сделать их уникальными, в то время как те, которые мы прошли в с тиром впереди, 'fromSql
и 'Person
сохраняются как их фактические определения.
Если вы не хотите использовать лямбда-выражения, вы можете использовать
runQ [d| makeModel [id,name] = Person (fromSql id) (fromSql name) |]
в качестве отправной точки - [d| ... |]
для определения функций.
'vals' - это список, наоборот? –
К сожалению, я должен был указать, 'vals' - это« Имя », а не список. –
Почему? 'ListE' принимает' [Exp] '. –