2012-05-18 44 views
5

我有很多這樣的代碼:哈斯克爾:轉換列表數據

data Post = 
    Post 
    { postOwner :: Integer 
    , postText :: ByteString 
    , postDate :: ByteString 
    } 

sqlToPost :: [SqlValue] -> Post 
sqlToPost [owner, text, date] = Post (fromSql owner) (fromSql text) (fromSql date) 

(庫這裏使用的是HDBC)。將來會有大量的數據,如Post,功能如sqlToVal。我可以減少sqlToVal的樣板代碼嗎?

+0

有不同的數據庫庫,將爲您做這樣的樣板代。但是,HDBC以爲您提供非常「原始」的數據庫接口而聞名,所以我不認爲有像您正在尋找的那種抽象。 – dflemstr

+0

好吧,我學習了Haskell,我發現這很有趣。你可以指點一些圖書館,我可以看到它如何解決? – demi

+2

有兩種解決方法:使用Template Haskell,它會爲每個數據類型生成一個不同的'sqlToSomething'函數,或者使用GHC Generics來告訴編譯器如何自動反序列化*任何*'數據'類型。 ['persistent'](http://hackage.haskell.org/package/persistent)庫廣泛使用第一種方法;有[教程](http://www.yesodweb.com/book/persistent)可用。對於HDBC也可能有解決方案,我只是沒有聽說過。我也可以告訴你如何用TH顯式地生成'sqlToBla'函數,沒有預先存在的代碼。 – dflemstr

回答

6

模板Haskell代碼生成是一個非常高級的話題。不過,如果您掌握了TH的藝術,可以使用上述技術來生成您要查找的代碼。

注意,下面的代碼將只data類型只有一個構造函數(例如不data Foo = A String Int | B String Int,它有兩個構造AB),因爲你怎麼不說應該在代碼處理工作。

我們將製作一個模板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's 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編譯使模板哈斯克爾):

{-# LANGUAGE TemplateHaskell #-} 

-- 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])