2017-04-15 1 views
4

J'ai choisi de centrer cette question autour des objets JSON et wl-pprint-annotated (here is the paper behind that library) parce qu'ils le rendre facile d'avoir un MVCE, mais mon problème est pas vraiment autour jolie impression seulement objets JSON et Je suis flexible pour quelle jolie bibliothèque j'utilise.objets JavaScript Jolie impression


Tenir compte du type de données objet JavaScript simplifié suivant:

data Object = Object [(String, Object)] 
      | String String 

Comment puis-je définir une fonction assez d'impression qui enveloppe sa sortie à plusieurs lignes de la manière habituelle? Ce que je veux dire par là est: la sortie joliment imprimée devrait, autant que possible, tenir sur une ligne. Quand ce n'est pas possible, je m'attends à ce que les objets les plus externes commencent à ajouter des nouvelles lignes avant les lignes internes.

Voici une tentative à l'aide wl-pprint-annotated:

{-# LANGUAGE OverloadedString #-} 
import Text.PrettyPrint.Annotated.WL 

prettyObject :: Object -> Doc a 
prettyObject (String str) = "\"" <> text str <> "\"" 
prettyObject (Object fields) = Union ("{" <+> hsep fields' <+> "}") 
            ("{" <#> indent 2 (vsep fields') <#> "}") 

    where 
    fields' :: [Doc a] 
    fields' = punctuate "," [ text key <> ":" <+> prettyObject val 
          | (key,val) <- fields ] 

Maintenant, certains cas de test.

ghci> o1 = Object [("key1", String "val1")] 
ghci> o2 = Object [("key2", String "val2"), ("looooooooooong key3", String "loooooooooooong val3"),("key4", String "val4")] 
ghci> o3 = Object [("key5", String "val5"), ("key6", o2), ("key7", String "val7")] 
ghci> prettyObject o1 
{ key1: "val1" } 
ghci> prettyObject o2 
{ 
    key2: "val2", 
    looooooooooong key3: "loooooooooooong val3", 
    key4: "val4" 
} 
ghci> prettyObject o3 
{ key5: { key1: "val1" }, key6: { 
    key2: "val2", 
    looooooooooong key3: "loooooooooooong val3", 
    key4: "val4" 
}, key7: "val7" } 

Je voudrais la dernière sortie à la place être

{ 
    key5: { key1: "val1" }, 
    key6: { 
    key2: "val2", 
    looooooooooong key3: "loooooooooooong val3", 
    key4: "val4" 
    }, 
    key7: "val7" 
} 

Je suis à la recherche d'une solution qui correspond en quelque sorte avec l'une des bibliothèques jolie impression existantes dans Haskell (en réalité, je suis joli-impression beaucoup plus qu'un simple sous-ensemble de JSON).

Je suis pas à la recherche d'une solution qui définit un prettyObject :: Object -> String - le point de l'ensemble de cette approche est que le rendu du Doc dépend de l'endroit où il est dans la grande image de ce qui est assez imprimé.

Répondre

3

La jolie bibliothèque d'impression que vous utilisez peut déjà le faire; (vous venez de lui dire de faire une chose différente!) généralement cette famille (WL) de jolies imprimantes gère assez bien cette affaire.

Notez le positionnement de votre Union:

prettyObject (Object fields) = Union <one line> <many line> 

Au point dans votre texte où vous faites logiquement le choix de rompre, ce qui est au début d'une paire clé-valeur, vous n'avez pas avoir un Union dans votre structure Doc. Le choix est fait au point où un bloc fermé {..} commence; et si vous examinez la sortie, c'est exactement ce qu'il vous donne:

{ key5: { key1: "val1" }, key6: { ----- line break here 
    key2: "val2", 

Vous avez besoin d'une fonction pour mettre en œuvre votre logique souhaitée pour les paires clé-valeur:

indent' k x = flatAlt (indent k x) (flatten x) 
prettyKVPair (k,v) = indent' 2 $ text k <> ":" <+> pretty v 

indent' est comme indent, mais fournit une alternative explicite qui n'est pas indentée. flatAlt fournit une alternative qui est utilisée lorsque le texte est aplati, et votre texte sera aplati par (vous l'aurez deviné) flatten.Vous devez également restructurer en conséquence prettyObject:

prettyObject :: Object -> Doc a 
prettyObject (Object fields) = sep $ "{" : fields' ++ [ "}" ] where 
    fields' = punctuate "," $ map prettyKVPair fields 
... 

Remarque il n'y a pas Union explicite, mais sep = group . vsep et group = \x -> Union (flatten x) x. Vous avez maintenant une union correspondant à des choix logiques sur l'endroit où vous aplatissez votre texte.

Le résultat:

>pretty o1 
{ key1: "val1" } 
>pretty o2 
{ 
    key2: "val2", 
    looooooooooong key3: "loooooooooooong val3", 
    key4: "val4" 
} 
>pretty o3 
{ 
    key5: "val5", 
    key6: { 
    key2: "val2", 
    looooooooooong key3: "loooooooooooong val3", 
    key4: "val4" 
    }, 
    key7: "val7" 
} 

En réponse à la question dans le commentaire, la façon de fournir une alternative à plat est d'utiliser flatAlt, bien sûr! Le seul problème ici est que vous voulez faire cela pour un seul élément (le dernier) d'une liste - mais c'est un problème avec les listes, pas Doc. N'hésitez pas à utiliser Data.Sequence ou tout autre Traversable, avec lequel la plupart des fonctions de type «liste» comme punctuate fonctionnent, si c'est une opération dont vous avez besoin beaucoup.

flattenedOf a b = flatAlt a (flatten b) # useful combinator 

trailingSep _ [] = [] 
trailingSep s xs = as ++ [ (a <> s) `flattenedOf` a ] 
    where as = init xs; a = last xs 

... 
prettyObject (Object fields) = <unchanged> where 
    fields' = trailingSep "," $ <unchanged> 
+0

Merci beaucoup! C'est vraiment une réponse/explication étonnante. – Alec