2017-04-15 80 views
4

我选择了围绕着JSON对象和wl-pprint-annotated这个问题(here is the paper behind that library),因为他们可以很容易地有MVCE,但我的问题是不实际周围漂亮的印刷只是 JSON对象和对于我使用的漂亮打印库,我很灵活。漂亮的印刷JavaScript对象


考虑以下简化的JavaScript对象数据类型:

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

如何可以定义一个封装其输出到多行以通常的方式相当打印功能?我的意思是:只要可能,合适的打印输出应该放在一行上。如果这是不可能的,我期望最外层的对象在内层之前开始添加新行。

下面是使用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 ] 

现在,一些测试用例。

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" } 

我想最后的输出,而不是成为

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

我找这在某种程度上与在Haskell现有的漂亮印刷库选择一个适合(在现实的解决方案,我漂亮的印刷很多不仅仅是JSON的一个子集)。

寻找它定义了prettyObject :: Object -> String的解决方案 - 这种方法的整个的一点是,Doc的呈现方式取决于它是在被漂亮的印刷什么大局。

回答

3

你正在使用的漂亮打印库已经可以做到这一点; (你刚刚告诉它做不同的事情!)通常这个美丽的打印机家族(WL)处理这种情况非常好。

注意你的Union的定位:

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

在你的文字的地步,你在逻辑上做出选择突破,这是在键值对的开始,你不在您的Doc结构中有Union。在封闭块开始的地方进行选择;如果你仔细检查输出,这正是它给你:

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

您需要一个函数来实现所需的逻辑键值对:

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

indent'就像indent,但提供了一个不缩进的明确选择。 flatAlt提供了一个替代方案,当文本变平时,文本将被平铺(您可能已经猜到)flatten。您还需要重新构造prettyObject相应:

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

注意没有明确Union,但sep = group . vsepgroup = \x -> Union (flatten x) x。你现在有一个联盟,对应于你在哪里拼合你的文本的逻辑选择。

结果:

>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" 
} 

针对在评论的问题,提供一个平坦的替代的方法是使用flatAlt,当然!这里唯一的问题是你想为列表的单个元素(最后一个)执行此操作 - 但这是列表问题,而不是Doc。随意使用Data.Sequence或任何其他Traversable,与大多数类似“列表”的功能,如punctuate工作,如果这是一个操作,你需要很多。

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

非常感谢!这真是一个了不起的答案/解释。 – Alec