2012-05-18 45 views
5

Tôi có rất nhiều mã như thế này:Haskell: Chuyển đổi danh sách dữ liệu

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

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

(Thư viện sử dụng ở đây là HDBC). Trong tương lai, sẽ có rất nhiều dữ liệu như Post và chức năng như sqlToVal. Tôi có thể giảm mã bản mẫu này cho sqlToVal không?

+0

Có các thư viện cơ sở dữ liệu khác nhau sẽ tạo ra bản mẫu soạn sẵn cho bạn. Tuy nhiên, HDBC được biết đến với việc cung cấp cho bạn một giao diện cơ sở dữ liệu rất "thô", vì vậy tôi không nghĩ rằng có một trừu tượng như một trong những bạn đang tìm kiếm. – dflemstr

+0

Vâng, tôi học Haskell và tôi thấy thú vị về mặt kỹ thuật này được thực hiện như thế nào. Bạn có thể chỉ một số thư viện nơi tôi có thể xem cách giải quyết này? – demi

+2

Có hai cách để giải quyết: Sử dụng mẫu Haskell để tạo ra một hàm 'sqlToSomething' khác cho mỗi kiểu dữ liệu, hoặc sử dụng GHC Generics để báo cho trình biên dịch biết cách tự động hóa * bất kỳ kiểu dữ liệu' 'nào'. Thư viện ['persistent'] (http://hackage.haskell.org/package/persistent) sử dụng phương thức đầu tiên rộng rãi; có [một hướng dẫn] (http://www.yesodweb.com/book/persistent) sẵn có. Cũng có thể là giải pháp cho HDBC, tôi chưa từng nghe về nó. Tôi cũng có thể chỉ cho bạn cách tạo các hàm 'sqlToBla' một cách rõ ràng, không có mã đã tồn tại trước đó, với TH. – dflemstr

Trả lời

6

Tạo mẫu mã Haskell là một chủ đề rất nâng cao. Tuy nhiên, nếu bạn nắm vững nghệ thuật TH thì có thể sử dụng kỹ thuật đã nói để tạo mã mà bạn đang tìm kiếm.

Lưu ý rằng đoạn mã sau sẽ chỉ làm việc cho data loại với chỉ có một nhà xây dựng (VD: không data Foo = A String Int | B String Int, trong đó có hai nhà xây dựng AB) vì bạn đã không nói như thế nào phải được xử lý trong các mã.

Chúng tôi sẽ tạo một hàm Haskell mẫu chạy vào thời gian biên dịch, lấy tên của kiểu dữ liệu và tạo hàm có tên sqlTo<nameofdatatype>. Chức năng này trông như thế này:

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

Bây giờ, chúng tôi sử dụng chức năng này như sau (Lưu ý pragma LANGUAGE cho phép Template Haskell):

{-# 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 

Nếu bạn muốn xem các chức năng được tạo ra, chỉ cần chuyển -ddump-splices tới GHC khi biên dịch. Đầu ra là một cái gì đó như thế này:

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]) 
Các vấn đề liên quan