漂亮的JavaScript对象

时间:2017-04-15 01:12:40

标签: haskell pretty-print

我选择围绕JSON对象和wl-pprint-annotatedhere is the paper behind that library)来解决这个问题,因为它们可以很容易地拥有一个MVCE,但我的问题实际上并不是在打印只是 JSON对象,我对我使用的漂亮打印库很灵活。

考虑以下简化的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的呈现取决于它在什么是漂亮印刷的大图。

1 个答案:

答案 0 :(得分:3)

您正在使用的漂亮的打印库已经可以执行此操作; (你刚刚告诉它做了另外一件事!)一般来说,这个漂亮的打印机系列(WL)可以很好地处理这个案例。

请注意Union的定位:

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

在您的文字中您正在逻辑地选择中断的位置(即键值对的开头),您Union中没有Doc } 结构体。选择是在{..}封闭块开始的位置进行的;如果你仔细检查输出,那就是它给你的东西:

{ 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,其中包含大多数类似于&#39;的列表。像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>