计算递归类型别名列表中的元素

Counting elements in recursive type alias lists

根据 elm-compiler 存储库中的递归类型别名提示,一种解决方案产生类型签名:

type alias Comment =
  { message : String
  , upvotes : Int
  , downvotes : Int
  , responses : Maybe Responses
  }

type Responses = Responses (List Comment)

(其中 responses 已扩展为在此处键入 Maybe Responses 以允许空响应列表)。

我对计算此类列表中的元素数量很感兴趣,尽管我当前的解决方案似乎比需要的复杂得多。

count : List Comment -> Int
count comments =
    let
        responses =
            List.concatMap (\c -> flatList c) comments
    in
    List.length responses


flatList : Comment -> List Comment
flatList root =
    let
        rest =
            case root.children of
                Just responseList ->
                    List.concatMap (\child -> flatList child) <| unwrapResponses responseList
                Nothing ->
                    []
    in
    root :: rest

unwrapResponses : Responses -> List Comment
unwrapResponses responses =
    case responses of
        Responses comments ->
            comments

实际上,这解开每个 Responses 子列表并递归地展平它。然后,对于每个父 Comments,我们将每个平面 Responses 列表连接在一起,最后得到这个列表的长度。

因为我对这个扁平化列表没有用处,所以我更愿意在列表中重复计算每个 List.length,然后对结果进行折叠或求和(或者,使用其他一些方法检索总元素数)。但是,我不确定如何在不返回 flatList 结果的情况下生成这样的解决方案。

听起来你需要一个专门的评论折叠功能。折叠是使用接受元素和某种累加器值的函数访问结构中的每个元素的想法,这样您就可以一步一步地保持某种状态。

旁注:我建议您将注释 responses 定义为 Responses 而不是 Maybe Responses,因为空列表和 Nothing 确实代表同样的事情。

您可以为这样的评论列表定义 foldl

foldl : (Comment -> b -> b) -> b -> List Comment -> b
foldl f =
    List.foldl
        (\c acc ->
            case c.responses of
                Responses responses ->
                    foldl f (f c acc) responses
        )

这意味着它将首先使用您的函数访问评论节点,然后 children 从左到右,一路累积结果。

要使用它来确定长度,您只需忽略注释并沿途增加一个计数器:

length : List Comment
length =
    foldl (\_ acc -> acc + 1) 0

您会看到 definition of List.length 的实现使用与 foldl 相同的想法。