5

我正在尝试通过提到的章节。在阅读和思考练习时,我遇到了一些困难。

  • 首先,不应该是fillnest函数的签名:: Int -> Doc -> String吗?我认为那本书是正确的——他们不应该。
  • 接下来,在练习 1 中是否意味着只有超出边距的行不应填充空格,或者如果至少出现一个这样的行,则不应处理整个文本?
  • 下一个问题是关于练习 2。我几乎完全不明白作者的意思。他们的意思可以有两种解释:要么我们应该产生类似的东西

    {"foo": 123,
     "bar": 456}
    

    这意味着在打开分隔符(大括号或括号)之后的第一个词素的缩进被记住,并且下一行以该缩进量缩进(然后第一个nest参数没有意义),或者我们应该产生 (with amount of indentation = 4)

    {
        "foo": {
            "baz": 123
        },
        "bar": 456
    }
    

    但是,如果用户忘记在打开/关闭分隔符之后/之前插入换行符,那就没有意义了。还是我们应该强制插入?可能吗?(我知道可以始终插入换行符,但是是否可以识别用户是否自己插入了换行符?)。

另请注意,我已要求不要添加更多数据构造函数来Doc键入。

4

1 回答 1

6

练习 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可以用来扔掉尾随的Linehcat最后可以把它[Doc]变成一个Doc

这个解决方案的缺点是它抛弃了 any 的扁平化版本Union

如果修改padLinereplicate字符'#'而不是' ',则可以看到以下输出:

*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

}

如您所知,换行符太多,但基本缩进似乎产生某种看起来不错的嵌套。

于 2015-09-02T07:00:44.383 回答