2017-04-15 77 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