鉴于您希望能够进行分组,并且还需要能够确保某些行没有被插入,我们为什么不使用库设计者在数据类型中而不是在代码中编码语义这一事实。这个绝妙的决定使其非常可重新设计。
数据类型使用构造函数对Doc
换行符进行编码Line :: Bool -> Doc
。Bool 表示删除行时是否省略空格。(当它们在那里时,行缩进。)让我们替换 Bool:
data LineBehaviour = OmitSpace | AddSpace | Keep
data Doc = ...
...
Line !LineBehaviour -- not Bool any more
语义即数据设计的美妙之处在于,如果我们将这些Bool
数据替换为LineBehaviour
数据,那么没有使用它但未更改地传递它的函数就不需要编辑。函数查看 Bool 因更改而中断的内容 - 我们将通过更改旧语义所在的数据类型来准确重写需要更改以支持新语义的代码部分。在我们完成所有我们应该做的更改之前,程序不会编译,而我们不需要接触不依赖于换行语义的代码行。万岁!
例如,renderPretty
使用Line
构造函数,但在模式Line _
中,所以我们可以不理会它。
首先,我们需要Line True
用Line OmitSpace
和Line False
替换Line AddSpace
,
line = Line AddSpace
linebreak = Line OmitSpace
但也许我们应该添加我们自己的
hardline :: Doc
hardline = Line Keep
我们也许可以使用使用它的二元运算符
infixr 5 <->
(<->) :: Doc -> Doc -> Doc
x <-> y = x <> hardline <> y
和垂直分隔符的等价物,我想不出比非常垂直的分隔符更好的名字:
vvsep,vvcat :: [Doc] -> Doc
vvsep = fold (<->)
vvcat = fold (<->)
行的实际删除发生在group
函数中。一切都可以保持不变,除了:
flatten (Line break) = if break then Empty else Text 1 " "
应该改为
flatten (Line OmitSpace) = Empty
flatten (Line AddSpace) = Text 1 " "
flatten (Line Keep) = Line Keep
就是这样:我找不到其他可以改变的地方!