2016-11-18 4 views
1

Je travaille sur une intégration API qui ignore l'existence de XML ou JSON en faveur de l'ajout de données de caractères. (Le format Metro2, si vous êtes intéressé)Modéliser un format série dans le système de type, comme Serviteur

Je simplifie, mais imaginez qu'une personne doit être sérialisé comme ceci:

  • pos 0, 4 caractères: Nombre d'octets dans le message
  • A
  • pos 5: 6: "PERSON" caractères codés dur
  • A pos 11: 20 caractères: Nom, aligné à gauche et rembourré espace
  • A pos 21: 8 caractères: Anniversaire, YYYYMMDD
  • A la position 29: 3 caractères: âge, aligné à droite et à zéros

Les champs numériques sont toujours alignés à droite et à zéros. Les champs de texte sont toujours alignés à gauche et remplis d'espace.

Par exemple:

"0032PERSONDAVID WILCOX  19820711035" 

Puis-je exprimer dans le système de type? Aime ce que fait servant? Quelque chose comme ça? Puis-je vérifier de façon statique que mon implémentation de la sérialisation correspond au type? Puis-je vérifier de manière statique que le décalage du troisième champ (Name) est 11? Que les longueurs des champs précédents totalisent 11? Je suppose que non, car cela semble nécessiter un support de type dépendant complet.

Est-ce sur la bonne voie?

instance ToMetro Age where 
    -- get the length into the type system using a type family? 
    field = Numeric '3 

    -- express how this is encoded. Would need to use the length from the type family. Or if that doesn't work, put it in the constructor. 
    toMetro age = Numeric age 

Mise à jour: Exemple d'une fonction que je voudrais valider statiquement:

personToMetro :: Person -> PersonMessage 
personToMetro p = error "Make sure that what I return is a PersonMessage" 
+1

Pouvez-vous donner un exemple d'une fonction dont vous voulez obtenir plus de garanties statiques? Comme le côté gauche et la signature de type au moins – jberryman

+0

Juste ajouté un exemple. Est ce que ça aide? –

Répondre

3

Juste pour vous donner une certaine inspiration, faites juste ce serviteur fait et ont différents types pour les différents combinateurs vous support:

{-# LANGUAGE GADTs, DataKinds, KindSignatures, TypeOperators, ScopedTypeVariables #-} 

module Seriavant where 

import GHC.TypeLits 
import Data.Proxy 
import Data.List (stripPrefix) 

data Skip (n :: Nat) = Skip deriving Show 
data Token (n :: Nat) = Token String deriving Show 
data Lit (s :: Symbol) = Lit deriving Show 

data (:>>) a b = a :>> b deriving Show 
infixr :>> 

class Deserialize a where 
    deserialize :: String -> Maybe (a, String) 

instance (KnownNat n) => Deserialize (Skip n) where 
    deserialize s = do 
     (_, s') <- trySplit (natVal (Proxy :: Proxy n)) s 
     return (Skip, s') 

instance (KnownNat n) => Deserialize (Token n) where 
    deserialize s = do 
     (t, s') <- trySplit (natVal (Proxy :: Proxy n)) s 
     return (Token t, s') 

instance (KnownSymbol lit) => Deserialize (Lit lit) where 
    deserialize s = do 
     s' <- stripPrefix (symbolVal (Proxy :: Proxy lit)) s 
     return (Lit, s') 

instance (Deserialize a, Deserialize b) => Deserialize (a :>> b) where 
    deserialize s = do 
     (x, s') <- deserialize s 
     (y, s'') <- deserialize s' 
     return (x :>> y, s'') 

trySplit :: Integer -> [a] -> Maybe ([a], [a]) 
trySplit 0 xs = return ([], xs) 
trySplit n (x:xs) = do 
    (xs', ys) <- trySplit (n-1) xs 
    return (x:xs', ys) 
trySplit _ _ = Nothing 

Ouais donc c'est assez spartiate, mais il vous permet déjà de faire

type MyFormat = Token 4 :>> Lit "PERSON" :>> Skip 1 :>> Token 4 

testDeserialize :: String -> Maybe MyFormat 
testDeserialize = fmap fst . deserialize 

qui fonctionne comme ceci:

*Seriavant> testDeserialize "1" 
Nothing 
*Seriavant> testDeserialize "1234PERSON Foo " 
Just (Token "1234" :>> (Lit :>> (Skip :>> Token "Foo "))) 

EDIT: Je suis complètement hors Transforme mal lu la question, et Sean réclame sérialisation, pas désérialisation ... Mais bien sûr, nous peut le faire aussi bien:

class Serialize a where 
    serialize :: a -> String 

instance (KnownNat n) => Serialize (Skip n) where 
    serialize Skip = replicate (fromIntegral $ natVal (Proxy :: Proxy n)) ' ' 

instance (KnownNat n) => Serialize (Token n) where 
    serialize (Token t) = pad (fromIntegral $ natVal (Proxy :: Proxy n)) ' ' t 

instance (KnownSymbol lit) => Serialize (Lit lit) where 
    serialize Lit = symbolVal (Proxy :: Proxy lit) 

instance (Serialize a, Serialize b) => Serialize (a :>> b) where 
    serialize (x :>> y) = serialize x ++ serialize y 

pad :: Int -> a -> [a] -> [a] 
pad 0 _x0 xs = xs 
pad n x0 (x:xs) = x : pad (n-1) x0 xs 
pad n x0 [] = replicate n x0 

(bien sûr, cela a horrible performance wi tout cela String concaténation etc.mais ce n'est pas le point ici)

*Seriavant> serialize ((Token "1234" :: Token 4) :>> (Lit :: Lit "FOO") :>> (Skip :: Skip 2) :>> (Token "Bar" :: Token 10)) 
"1234FOO Bar  " 

Bien sûr, si nous savons le format, nous pouvons éviter les annotations de type embêtants:

type MyFormat = Token 4 :>> Lit "PERSON" :>> Skip 1 :>> Token 4 

testSerialize :: MyFormat -> String 
testSerialize = serialize 
*Seriavant> testSerialize (Token "1234" :>> Lit :>> Skip :>> Token "Bar") 
"1234PERSON Bar " 
+0

Ceci est doux, et super utile, merci! Pour être clair, je n'ai pas besoin de désérialiser ce format, seulement le sérialiser. –

+0

@SeanClarkHess ouais je devrais peut-être essayer de lire des questions la prochaine fois avant de leur répondre;) – Cactus

+0

Donc, je vois comment utiliser les valeurs Nat à l'exécution. Est-il possible de les utiliser pour vérifier statiquement que les décalages s'alignent? (Voir dans ma question, ci-dessus) –