练习 1
自己翻阅这本书,我被困在如何解释第一个练习的要求上。经过一番搜索,我发现了这种解决问题的方法。
这个想法是,每次遇到一个Line
,你先垫出Doc
之前的Line
。
Barry Allison 发布的解决方案似乎<>
在 HTML 格式中丢失了一些字符,但如果我将它们放回我猜它们应该去的地方,他的代码似乎可以工作。
该解决方案的缺点是它不填充最后一行,所以我修改了实现来做到这一点:
fill :: Int -> Doc -> Doc
fill width x = hcat (init (scanLines 0 [x <> Line]))
where
scanLines _ [] = []
scanLines col (d:ds) =
case d of
Empty -> scanLines col ds
Char c -> Char c : scanLines (col + 1) ds
Text s -> Text s : scanLines (col + length s) ds
Line -> Text (padLine (width - col)) : Line : scanLines 0 ds
a `Concat` b -> scanLines col (a:b:ds)
_ `Union` b -> scanLines col (b:ds)
padLine w = replicate w ' '
为了填充最后一行,该fill
函数首先将 a 附加Line
到输入,然后调用scanLines
. 与 Barry Allison 解决方案的不同之处在于,在此实现scanLines
中,类型为Int -> [Doc] -> [Doc]
. 这意味着init
可以用来扔掉尾随的Line
,hcat
最后可以把它[Doc]
变成一个Doc
。
这个解决方案的缺点是它抛弃了 any 的扁平化版本Union
。
如果修改padLine
为replicate
字符'#'
而不是' '
,则可以看到以下输出:
*PrettyJSON> let value = renderJValue (JObject [("f", JNumber 1), ("q", JBool True)])
*PrettyJSON> putStrLn (pretty 20 (Prettify.fill 30 value))
{"f": 1.0,####################
"q": true#####################
}#############################
这个练习可能有一个更惯用和更优雅的解决方案,但我是根据我到目前为止从阅读本书中学到的知识发布的。
练习 2
在第二个练习中,我对要求进行了一些自由的解释。正如其他人所指出的,在这里和其他地方,要求并不明确。
那么,以下更多是解决方案的草图:
nest :: Int -> Doc -> Doc
nest indentation x = indent 0 [x]
where
indent _ [] = Empty
indent nestLevel (d:ds) =
case d of
Empty -> indent nestLevel ds
Char '{' -> padLine nestLevel <> Char '{' <> indent (nestLevel + 1) (Line:ds)
Char '}' -> padLine (nestLevel - 1) <> Char '}' <> indent (nestLevel - 1) ds
Char '[' -> padLine nestLevel <> Char '[' <> indent (nestLevel + 1) (Line:ds)
Char ']' -> padLine (nestLevel - 1) <> Char ']' <> indent (nestLevel - 1) ds
Char c -> Char c <> indent nestLevel ds
Text s -> Text s <> indent nestLevel ds
Line -> padLine nestLevel <> indent nestLevel ds
a `Concat` b -> indent nestLevel (a:b:ds)
a `Union` b -> indent nestLevel (a:ds) `Union` indent nestLevel (b:ds)
padLine nl = Line <> Text (replicate (nl * indentation) ' ')
很可能存在边界情况,以及与pretty
书中函数的微妙交互,这可能意味着这不是一个完整的解决方案,但在这一点上,我已经在这方面花费了太多时间,我不觉得我正在从与不清楚的要求作斗争中学习 Haskell。
这是一个示例 GHCI 交互,演示了它是如何工作的:
*PrettyJSON> let value = renderJValue (JObject [("foo", (JObject [("baz", JNumber 123)])), ("bar", JNumber 456)])
*PrettyJSON> putStrLn (pretty 10 (Prettify.nest 4 value))
{
"foo":
{
"baz": 123.0
},
"bar": 456.0
}
如您所知,换行符太多,但基本缩进似乎产生了某种看起来不错的嵌套。