Бинарная сериализация в Haskell

Октябрь 06, 2009, 06:05

Для би­нар­ной се­ри­а­ли­за­ции в Haskell су­ще­ству­ет мо­дуль Data.Bi­na­ry. Без по­боч­ных эф­фек­тов (де)се­ри­а­ли­зу­ет в/из Data.ByteString.Lazy. Он пока не идет в по­став­ке с GHC и его нуж­но ру­ка­ми ста­вить из hack­age. Ис­поль­зо­вать его не про­сто, а очень про­сто:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module Main where

import Control.Monad
import Data.Binary

data SomeData = ConstrA | ConstrB Int | ConstrC String Int
deriving(Show)

instance Binary SomeData where
put ConstrA = sequence_ [put (0 :: Word8)]
put (ConstrB a) = sequence_ [put (1 :: Word8), put a]
put (ConstrC a b) = sequence_ [put (2 :: Word8), put a, put b]
get = do
tag <- getWord8
case tag of
0 -> do
return ConstrA
1 -> do
a <- get
return $ ConstrB a
2 -> do
a <- get
b <- get
return $ ConstrC a b

Ну и по­иг­рать­ся:

> encode $ ConstrC "asdf" 1
Chunk "\STX\NUL\NUL\NUL\NUL\NUL\NUL\NUL\EOTasdf\NUL\NUL\NUL\NUL\NUL\NUL\NUL\SOH" Empty

> decode (encode $ ConstrC "asdf" 1) :: SomeData
ConstrC "asdf" 1

Всё по­нят­но. Но вот толь­ко если у ва­ших ти­пов мно­го кон­струк­то­ров и са­мих ти­пов мно­го, на­пи­са­ние этих бес­ко­неч­ных ин­стан­сов пре­вра­ща­ет­ся в аб­со­лют­но ту­пую ра­бо­ту, а мы, гор­дые хас­кел­ли­сты, не лю­бим де­лать ту­пую ра­бо­ту! Да и руч­ное на­пи­са­ние та­ко­го кода чре­ва­та де­ла­ни­ем боль­шо­го ко­ли­че­ства оши­бок — тут вам ум­ный вы­вод ти­пов ни­чем не по­мо­жет. Соб­ствен­но в до­ку­мен­та­ции Data.Bi­na­ry есть ре­ко­мен­да­ция ис­поль­зо­вать для со­зда­ния ин­стан­сов скрипт ге­не­ра­ции хас­кель­но­го кода по де­кла­ра­ции типа. Ме­тод, пря­мо ска­жем, ни­ку­да не год­ный: для каж­до­го из­ме­не­ния типа при­дет­ся руч­ка­ми за­но­во ге­не­ри­ро­вать ин­станс, мож­но ко­неч­но до­то­чить этот скрипт до ав­то­ма­ти­че­ской ге­не­ра­ции, но это чре­ва­то ко­вы­ря­ни­ем с ва­ши­ми бил­до­вы­ми скрип­та­ми и т.п. без­об­ра­зи­я­ми. Ко­ро­че — неудоб­но.

В эту за­да­чу пря­мо таки про­сить­ся ме­та­про­грам­ми­ро­ва­ние. А что у нас в Хас­кел­ле ис­поль­зу­ет­ся для ме­та­про­грам­ми­ро­ва­ния? Пра­виль­но — Tem­plate Haskell.

UPD: Ука­зан­ный да­лее спо­соб ис­поль­зо­вать не ре­ко­мен­дую, чи­тай­те как надо.

Судя по кешу гуг­ла и по хас­кел­лев­ской вики, нечто по­хо­жее было на­пи­са­но в биб­лио­те­ке SerTH, од­на­ко ссыл­ка на неё ве­дет в 403 For­bid­den и вы­ко­вы­рян­ные мною его ис­ход­ни­ки из кеша гуг­ла под GHC 6.10.4 не со­би­ра­лись, и за пол-часа я это не по­бе­дил. Из фак­та от­сут­ствия дру­гих ре­а­ли­за­ций ге­не­ра­ции Bi­na­ry на Tem­plate Haskell мож­но сде­лать ряд вы­во­дов:

  • Я са­мый гор­дый из всех гор­дых хас­ке­ли­стов — все осталь­ные обо­жа­ют де­лать ту­пую ра­бо­ту
  • Все пи­шут этот несчаст­ный ав­то­ге­не­ра­тор на TH и не вы­кла­ды­ва­ют его в Ин­тер­нет (за­сран­цы!)
  • Хас­кель — ни­ко­му не нуж­ный язык, на нем ни­кто ни­че­го не пи­шет слож­нее вы­чис­ле­ния фак­то­ри­а­ла
  • Этот ге­не­ра­тор ле­жит на са­мом вид­ном ме­сте и я его не за­ме­тил
Я за­су­чил ру­ка­ва и по­шел пи­сать ге­не­ра­тор сам. По­лу­чи­лось ко­неч­но страш­нень­ко, зато ра­бо­та­ет! Для учеб­ных це­лей этот код ис­поль­зо­вать бес­смыс­лен­но, так что осо­бо я про него пи­сать не буду:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
module DeriveBinary(derive_binary) where

import Language.Haskell.TH
import Language.Haskell.TH.Syntax

derive_binary :: Name -> Q [Dec]
derive_binary t = do
TyConI (DataD _ _ _ cnst _) <- reify t
return [InstanceD [] (AppT (ConT $ mkName "Data.Binary.Binary") (ConT t)) [
FunD (mkName "put") $ make_clause_list cnst,
ValD (VarP $ mkName "get") (
NormalB (
DoE [
BindS (VarP $ mkName "tag")
(VarE $ mkName "Data.Binary.Get.getWord8"),
NoBindS (CaseE (VarE $ mkName "tag")
(make_match_list cnst))])) []]]
where
make_clause_list cnst = map make_clause $ zip cnst [0..]
make_clause (c,cidx) = Clause [ConP (name c) (map VarP $ make_var_names c)]
(NormalB (AppE (VarE $ mkName "Control.Monad.sequence_") (
ListE $ (++)
[AppE (VarE $ mkName "Data.Binary.put") (
SigE (LitE (IntegerL cidx))
(ConT $ mkName "Word8"))] $
map make_put_var $ make_var_names c))) []
make_var_names c = map (\i -> mkName $ (++) "a_" $ show i)
[1..(len_of_args c)]
make_put_var v = AppE (VarE $ mkName "Data.Binary.put") (VarE v)
make_match_list cnst = map make_match $ zip cnst [0..]
make_match (c,cidx) = Match (LitP (IntegerL cidx)) (
NormalB (
DoE ((make_binds c) ++
[NoBindS (
InfixE
(Just (VarE $ mkName "return"))
(VarE $ mkName "$") (Just ( make_constr c )))]))) []
make_binds c = map (\n -> BindS (VarP $ mkName $ (++) "a_" $ show n)
(VarE $ mkName "Data.Binary.get")) [1..(len_of_args c)]
make_constr c = foldl (\p n -> AppE p (VarE $ mkName $ (++) "a_" $ show n))
(ConE $ name c) [1..(len_of_args c)]
name (NormalC n _) = n
len_of_args (NormalC _ l) = length l

Ис­поль­зу­ем:

1
2
3
4
5
6
7
8
9
10
11
12
13
{-# LANGUAGE TemplateHaskell #-}

module Main where

import DeriveBinary
import qualified Control.Monad
import Data.Binary
import qualified Data.Binary.Get

data SomeData = ConstA | ConstrB Int | ConstrC String Int
deriving(Show)

$(derive_binary ''SomeData)
Кра­со­та! Те­сти­ру­ем:
> encode $ ConstrC "asdf" 1
Chunk "\STX\NUL\NUL\NUL\NUL\NUL\NUL\NUL\EOTasdf\NUL\NUL\NUL\NUL\NUL\NUL\NUL\SOH" Empty

> decode (encode $ ConstrC "asdf" 1) :: SomeData
ConstrC "asdf" 1

Кому надо — поль­зуй­тесь. Ли­цен­зия: Pub­lic Do­main. Ска­чать тут.

За­ра­нее по­жа­луй­ста! :)

blog comments powered by Disqus
Сергей Лымарь © 2005-2014, Все права защищены.