我正试图通过mentioned chapter。在阅读和思考练习时,我遇到了一些困难。
fill
和nest
函数的签名不应该是:: Int -> Doc -> String
?我认为这本书是正确的 - 他们不应该。 下一个问题是关于练习2.我几乎完全不明白作者的意思。对于它们的含义可以有两种解释:要么我们应该产生类似
的东西{"foo": 123,
"bar": 456}
意味着记住打开分隔符(大括号或括号)后第一个词位的缩进,下一行用缩进量缩进(然后第一个nest
参数没有意义),或者我们应该生产(amount of indentation = 4
)
{
"foo": {
"baz": 123
},
"bar": 456
}
但是如果用户忘记在打开/关闭分隔符之后/之前插入换行符是没有意义的。或者我们应该强行插入?可能吗? (我知道可以随时插入换行符,但是可以识别用户是否自己插入了换行符吗?)。
另请注意,我已要求不向Doc
类型添加更多数据构造函数。
答案 0 :(得分:4)
自己完成这本书,我被困在如何解释第一次练习的要求。经过一番搜索,我点击了this approach to the problem。
我们的想法是,每次遇到Line
时,您首先会在<{em> 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
函数首先将Line
附加到输入,然后调用scanLines
。与Barry Allison的解决方案的不同之处在于,在此实现中,scanLines
具有类型Int -> [Doc] -> [Doc]
。这意味着init
可以用来丢弃尾随Line
,而hcat
最终可以将[Doc]
变成Doc
。
此解决方案的缺点是它会抛弃任何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#####################
}#############################
这个练习可能会有更为惯用和优雅的解决方案,但我根据我迄今为止从阅读本书中学到的东西发布了这个。
当谈到第二次练习时,我采取了一些解释要求的自由。正如其他人在这里和其他地方所指出的那样,要求并不清楚。
以下,更多的是解决方案草图:
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
}
正如你所知,有太多的换行符,但基本的缩进似乎产生了一些类似看起来很好的嵌套。