真实世界Haskell,第5章,练习:如何填写所有要求以及它们到底是什么?

时间:2013-10-25 08:51:00

标签: haskell

我正试图通过mentioned chapter。在阅读和思考练习时,我遇到了一些困难。

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

    的东西
    {"foo": 123,
     "bar": 456}
    

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

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

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

另请注意,我已要求不向Doc类型添加更多数据构造函数。

1 个答案:

答案 0 :(得分:4)

练习1

自己完成这本书,我被困在如何解释第一次练习的要求。经过一番搜索,我点击了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#####################
}#############################

这个练习可能会有更为惯用和优雅的解决方案,但我根据我迄今为止从阅读本书中学到的东西发布了这个。

练习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

}

正如你所知,有太多的换行符,但基本的缩进似乎产生了一些类似看起来很好的嵌套。