是否可以使用复合模式从树生成HTML并处理缩进,或者这本来就不可能?

我在复合模式上看了这个视频,其中主要的例子是如何使用模式作为一种手段,从描述待办事项列表的树结构中生成 HTML 代码,其中每个项目都可以依次成为待办事项列表,看起来很方便测试台,所以这里是一个目标 HTML:

[ ] Main
<ul>
  <li>[ ] 1.</li>
  <li>[ ] 2.
    <ul>
      <li>[ ] 2.1</li>
      <li>[ ] 2.2</li>
    </ul>
  </li>
  <li>[ ] 3.</li>
</ul>

(对不起,如果顶部[ ] Main没有意义,但我不知道 HTML;此外,我相信这与我的问题无关。)

我知道设计模式主要是一个面向对象的“东西”,但是我经常参考Haskell 中的设计模式一文来了解如何在函数式编程中重新解释它们,目的是在更深层次上理解它们。

关于复合模式,那篇文章基本上是这样写的:

合成的。递归代数数据类型。特别突出,因为没有内置继承。

因此,我认为在 Haskell 中尝试它会很容易,并且我想出了以下代码:

import Data.List (intercalate)

data Todo = Todo String | TodoList String [Todo] deriving Show

showList' :: Todo -> String
showList' (Todo s) = "[ ] " ++ s
showList' (TodoList s ts) = "[ ] " ++ s
                          ++ "<ul><li>"
                          ++ intercalate "</li><li>" (map showList' ts)
                          ++ "</li></ul>"

哪个,像这样喂

putStrLn $ showList' $ TodoList "Main" [Todo "1.", TodoList "2." [Todo "2.1", Todo "2.2"], Todo "3."]

生成这个输出

[ ] Main<ul><li>[ ] 1.</li><li>[ ] 2.<ul><li>[ ] 2.1</li><li>[ ] 2.2</li></ul></li><li>[ ] 3.</li></ul>

这本质上是我的问题顶部的 HTML 呈现在一行中:从我的实现中showList'可以清楚地看出,一旦调用它(在任何深度的回避)返回一个字符串,该字符串不会以任何方式改变,只是与其他人连接。所以我觉得我无能为力来showList'添加n和空格来达到格式良好的 HTML。

我已经尝试了一点,添加了空格和n,但尤其是在阅读Mark Seemann 的Composite as a monoid 时,我开始有点怀疑我正在尝试做的事情的可行性......

我很想得出这样的结论,如果复合是幺半群,这意味着无论它们在树中的深度如何,各种项目都以相同的方式两两组合,因此这意味着为漂亮的格式添加空间是不可能,因为要添加的空间量取决于被连接的两个元素周围的上下文,而不仅仅是两个元素。

但是,我不太确定我的推理,因此我在这里问。

回答

这个答案有点绕。(评论中已经包含了一个完全有效且更直接的建议。)

我们可以定义这个辅助类型:

data Todo' a = Todo' String 
             | TodoList' String [a] 
             deriving Show

就像Todo,但是在“递归”步骤中Todo,我们有一个多态值,而不是另一个。我们可以在那里放任何我们想要的东西,包括原始的Todo

peel :: Todo -> Todo' Todo
peel todo = case todo of
    Todo s -> Todo' s 
    TodoList s xs -> TodoList' s xs 

我们到底为什么要这样做?好吧,有时我们想讨论递归数据类型的单个“层”,而未解决下面的层可能包含什么的问题。

现在我们要以showList'另一种方式重建。首先,这个辅助功能cata

cata :: (Todo' a -> a) -> Todo -> a
cata f todo  = case peel todo of 
    Todo' s -> f (Todo' s)
    TodoList' s xs -> f (TodoList' s (map (cata f) xs))

该函数表示,如果我们有办法将Todo'带有某种结果的单个层从较低层转换为当前层的结果,那么我们就可以将整个Todo值转换为结果。

showList' 现在可以写成

showList'' :: Todo -> String
showList'' todo = cata layer todo
  where
  layer :: Todo' String -> String
  layer (Todo' s) = "[ ] " ++ s
  layer (TodoList' s xs) = "[ ] " ++ s
                           ++ "<ul><li>"
                           ++ intercalate "</li><li>" xs
                           ++ "</li></ul>"

请注意,此版本没有显式递归,cata会处理它。

好的。现在,正如您所提到的,缩进的问题是一层的结果取决于上面的层数。在 Haskell 中表达这种依赖的最自然的方式是使用 type 函数Int -> String,其中Int是上面的层数。

当我们写的时候showList',我做了catareturn a String。如果我们让它返回一个函数Int -> String呢?

showIndented :: Todo -> String
showIndented todo = cata layer todo 0
  where
  layer :: Todo' (Int -> String) -> Int -> String
  layer todo' indentation = 
    let tabs =  replicate indentation 't'
     in case todo' of
        Todo' s -> 
            tabs ++  "<li>[ ] " ++ s ++ "</li>n"
        TodoList' s fs ->
            tabs ++ "[ ] " ++ s ++ "n" ++
            tabs ++ "<ul>n" ++ 
            foldMap ($ succ indentation) fs ++ 
            tabs ++ "</ul>n"

foldMap ($ succ indentation) xs位采用函数列表,使用当前缩进级别 + 1 调用所有函数,并连接结果字符串。


以上是是否可以使用复合模式从树生成HTML并处理缩进,或者这本来就不可能?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>