2012-10-14 36 views
0

讓我們先從以下Haskell的數據序列化類

data A = A String deriving Show 
data B = B String deriving Show 

class X a where 
    spooge :: a -> Q 

[ Some implementations of X for A and B ] 

現在讓我們說我們有節目的定製實現,讀,取名秀「和讀」其分別利用展會作爲序列化機制。我想秀「和讀」有類型

show' :: X a => a -> String 
read' :: X a => String -> a 

所以我可以做的事情一樣

f :: String -> [Q] 
f d = map (\x -> spooge $ read' x) d 

如果數據可能已經

[show' (A "foo"), show' (B "bar")] 

綜上所述,我想序列化的東西各種類型共享一個常見的類型類型,所以我可以自動調用他們在反序列化的東西上的單獨實現。

現在,我知道你會寫一些模板哈斯克爾這將產生一個包裝的類型,比如

data XWrap = AWrap A | BWrap B deriving (Show) 

和序列化包裝類型這將保證該類型信息將被存儲下來,和我們'd能夠讓自己回到至少一個XWrap ......但是有沒有更好的方式使用哈斯克爾忍者?

編輯

好吧我需要更具體的應用程序。這是一個API。用戶將按照他們認爲合適的方式定義他們的As,Bs和fs。我永遠不希望他們通過更新其XWraps或開關或其他任何代碼的其餘代碼進行黑客攻擊。我願意妥協的最多的是某種格式的所有A,B等的某個列表。爲什麼?

這是應用程序。 A是「從FTP服務器下載文件」。 B是「從flac轉換到mp3」。 A包含用戶名,密碼,端口等信息。 B包含文件路徑信息。可能有許多As和Bs。數百人。許多人願意編入該計劃。兩個只是一個例子。 A和B是X,X應該被稱爲「門票」。 Q是IO()。 Spooge是runTicket。我想將這些票據讀入相關的數據類型中,然後編寫一些通用代碼,這些通用代碼將對從磁盤上的內容讀取的內容運行Ticket。在某些時候,我不得不將類型信息插入到序列化數據中。

回答

4

如果你真的想要的是一個異構列表,然後使用存在類型。如果你想序列化,然後使用穀物+字節串。如果你想動態輸入,這是我認爲你的實際目標是什麼,然後使用Data.Dynamic。如果這些都不是你想要的,或者你想讓我擴大,請按井號鍵。

根據你的編輯,我沒有看到任何理由,thunk的列表將無法正常工作。 IO()不能代表「從FTP服務器下載文件」和「從FLAC轉換爲MP3」的操作?

+3

哦,宕,我有我的鍵盤:( –

+0

只有美元和歐元鍵..或者,如果你想將其存儲在某種有限的地圖快查找,請按哈希鍵。 – AndrewC

+0

除了不使用存在類型:) – singpolyma

3

我首先要強調,我們所有的幸福聽衆在那裏,XWrap一個很好的方法,而且很多時候,你可以寫一個自己不是使用模板哈斯克爾寫更快。

你說你可以拿回「至少一個XWrap」,彷彿這意味着你無法恢復的類型AXWrapB或者你不能對他們使用你的類型類。不對!你甚至可以定義

separateAB :: [XWrap] -> ([A],[B]) 

如果你不想讓它們混合在一起,你應該單獨連載它們!

這比haskell ninja-ery更好;也許你不需要處理任意的實例,也許只是你所做的。


真的需要您的原稿類型回來?如果您想使用存在類型,因爲您只想要spooge您的反序列化數據,爲什麼不將其序列化爲Q本身,或者有一些中間數據類型PoisedToSpooge,您可以反序列化,爲您提供真正需要的所有數據仿製。爲什麼不把它作爲X的實例呢?

您可以將方法添加到X類中,該類將轉換爲PoisedToSpooge

你可以稱之爲toPoisedToSpooge這樣有趣的東西,它脫離舌頭很好,你不覺得嗎? :)

無論如何,這將在同一時間刪除您的類型系統的複雜性在

f d = map (\x -> spooge $ read' x) d -- oops, the type of read' x depends on the String 

解決惱人的曖昧類型可以與

stringToPoisedToSpoogeToDeserialise :: String -> PoisedToSpooge -- use to deserialise 

更換read'和定義

f d = map (\x -> spooge $ stringToPoisedToSpoogeToDeserialise x) -- no ambiguous type 

我們當然可以寫作更多succincly作爲

f = map (spooge.stringToPoisedToSpoogeToDeserialise) 

雖然我在這裏意識到了諷刺意味,所以建議讓代碼更簡潔。 :)

+0

我將它寫入模板haskell的原因是因爲這是一個API,而不是因爲它需要一段時間才寫。我已經更新了上面的問題。 – Evan

+1

呼叫良好; TH在那個環境中是合適的。 – AndrewC

0

只需使用Either。您的用戶甚至不必自行包裝。你有你的反序列化器爲你包裝Either。我不知道你的系列化協議是什麼,但我認爲你有一些方法來檢測這樣的要求,和下面的示例假設第一個字節的兩個請求區分:

deserializeRequest :: IO (Either A B) 
deserializeRequest = do 
    byte <- get1stByte 
    case byte of 
     0 -> do 
      ... 
      return $ Left $ A <A's fields> 
     1 -> do 
      ... 
      return $ Right $ B <B's fields> 

然後你不甚至不需要輸入類型spooge。只是使它的Either A B功能:

spooge :: Either A B -> Q 
+0

這不起作用。可能有八千個As和Bs。這是一個關於如何實現API的問題。我試圖編輯我的問題,試圖更清楚。 – Evan

+0

這不會改變我的答案。你的反序列化器返回'[或者b]'然後你只需要'map spooge'。與以前一樣,解串器負責將值封裝在適當的「Right」或「Left」中。 –

+0

Nono,如在那裏可能有Cs,Ds,Fs。加。對於每個A,B,C等,這些x - > do case中的每一個都必須用手寫出,這是我不想要的。我意識到模板haskell可以做到這一點,但是如果可以得到幫助,寧可不採取這種方法。 – Evan

1

我假設你想deserialised門票 不是運行他們做更多的事情,因爲如果不是你不妨詢問用戶提供了一堆String -> IO() 或類似的,根本不需要任何聰明的東西。

如果是這樣,萬歲!我不經常覺得它適合推薦這樣的高級語言功能。

class Ticketable a where 
    show' :: a -> String 
    read' :: String -> Maybe a 
    runTicket :: a -> IO() 
    -- other useful things to do with tickets 

這一切都取決於read'的類型。 read' :: Ticket a => String -> a不是很有用, ,因爲它可以處理無效數據的唯一方法是崩潰。 如果我們更改類型爲read' :: Ticket a => String -> Maybe a可以允許我們從磁盤讀取和 嘗試所有的可能性或完全失敗。 (或者你可以使用一個解析器:parse :: Ticket a => String -> Maybe (a,String)

讓我們用一個GADT給我們ExistentialQuantification沒有語法和具備更好的錯誤信息:

{-# LANGUAGE GADTs #-} 

data Ticket where 
    MkTicket :: Ticketable a => a -> Ticket 

showT :: Ticket -> String 
showT (MkTicket a) = show' a 

runT :: Ticket -> IO() 
runT (MkTicket a) = runTicket a 

通知的MkTicket contstuctor如何提供上下文Ticketable a爲自由! GADT很棒。

使Ticketable和Ticketable的實例很好,但這不起作用,因爲會隱藏 模糊a隱藏在其中。讓我們來看看可讀取票務類型的函數,並讓它們讀取 票。

ticketize :: Ticketable a => (String -> Maybe a) -> (String -> Maybe Ticket) 
ticketize = ((.).fmap) MkTicket -- a little pointfree fun 

你可以使用一些不尋常的定點字符串,如 "\n-+-+-+-+-+-Ticket-+-+-+-Border-+-+-+-+-+-+-+-\n"分離您的序列化的數據或更好,使用單獨的文件 乾脆。在這個例子中,我將使用「\ n」作爲分隔符。

readTickets :: [String -> Maybe Ticket] -> String -> [Maybe Ticket] 
readTickets readers xs = map (foldr orelse (const Nothing) readers) (lines xs) 

orelse :: (a -> Maybe b) -> (a -> Maybe b) -> (a -> Maybe b) 
(f `orelse` g) x = case f x of 
    Nothing -> g x 
    just_y -> just_y 

現在讓我們擺脫Just S和忽略Nothing S:

runAll :: [String -> Maybe Ticket] -> String -> IO() 
runAll ps xs = mapM_ runT . catMaybes $ readTickets ps xs 

讓我們做一個簡單的票,只是打印一些目錄

newtype Dir = Dir {unDir :: FilePath} deriving Show 
readDir xs = let (front,back) = splitAt 4 xs in 
     if front == "dir:" then Just $ Dir back else Nothing 

instance Ticketable Dir where 
    show' (Dir p) = "dir:"++show p 
    read' = readDir 
    runTicket (Dir p) = doesDirectoryExist p >>= flip when 
     (getDirectoryContents >=> mapM_ putStrLn $ p) 

和內容更瑣碎的票

data HelloWorld = HelloWorld deriving Show 
readHW "HelloWorld" = Just HelloWorld 
readHW _ = Nothing 
instance Ticketable HelloWorld where 
    show' HelloWorld = "HelloWorld" 
    read' = readHW 
    runTicket HelloWorld = putStrLn "Hello World!" 

,然後把它放在一起:

myreaders = [ticketize readDir,ticketize readHW] 

main = runAll myreaders $ unlines ["HelloWorld",".","HelloWorld","..",",HelloWorld"]