【问题标题】:How to stop infinite pattern-matching in Haskell?如何停止 Haskell 中的无限模式匹配?
【发布时间】:2016-10-28 06:34:32
【问题描述】:

我正在 http://www.cis.upenn.edu/~cis194/spring13/lectures.html 中做作业 2 中的练习 3

build :: [LogMessage] -> MessageTree
build [x] = Node Leaf x Leaf
build [x,y] = Node (build [x]) y Leaf
build [x,y,z] = Node (Node (build [x]) y Leaf) z Leaf

我有一个问题,我不知道如何结束模式匹配。它会继续扩展到[x,y,z,_],然后是[x,y,z,_,_],依此类推。我该如何阻止这种情况?

【问题讨论】:

  • 这是一个左折叠 - 即见this graphic
  • 最近我自己遇到了一个相关的问题,试图将我在列表到树函数中的临时分配推送到 GHC 的评估堆栈上,这有点有趣。最后,我得出结论,唯一的方法可能是为每个树脊形状设置一条线,这方式太多线了。

标签: haskell design-patterns tree matching infinite


【解决方案1】:

当您使用包含列表的函数时,您通常希望使用递归和列表元素函数。

让我们来看看你目前拥有什么。

在第四行,当它收到一个包含三个元素的列表时,你已经构建了以下内容:

build [x,y,z] = Node (Node (build [x]) y Leaf) z Leaf

在第三行,当它收到一个包含两个元素的列表时,您的构建评估如下:

build [x,y] = Node (build [x]) y Leaf

如果您查看这两行,您会注意到其中有一些重复。具体来说,两者都包含Node (build [x]) y Leaf。当有这样的重复时,递归会有所帮助。

使用递归,您可以使用包含两个元素的列表递归调用构建函数来替换重复。例如,您可以将第四行替换为:

build [x,y,z] = Node (build [x,y]) z Leaf

这让它稍微简单了一点,但还没有解决问题。

build :: [LogMessage] -> MessageTree
build [x] = Node Leaf x Leaf
build [x,y] = Node (build [x]) y Leaf
build [x,y,z] = Node (build [x,y]) z Leaf

如果您查看新函数,您会发现我们正在使用的一些常量可以通过使用列表元素函数来替换,例如 headtaillast 和 @987654330 @。

在函数的每个模式中,我们只分别处理列表的最后一个元素。然后将非最后一个元素传递回build。因此,我们可以通过使用last 访问列表的最后一个元素,并使用init 获取除最后一个元素之外的所有元素来摆脱一些模式匹配。

build :: [LogMessage] -> MessageTree
build [x] = Node Leaf x Leaf
build l = Node (build (init l)) (last l) Leaf

通过使用initlast 处理一般列表元素,而不是使用明确指定的列表元素,我们可以消除对无限模式匹配的需要。

【讨论】:

  • 在这种情况下,由于您从最后一个到第一个处理列表元素,(x:xs) 列表模式匹配不能直接使用。但一般以后你应该尽量让你的函数从第一个元素到最后一个元素工作,这样你就可以利用(x:xs)列表模式匹配来使函数更容易编写和理解。
  • @Amorris 反复调用init 是二次时间行为(在严格的语言中......),这是我试图避免的。相关:stackoverflow.com/questions/19944969/…,解释了一种常见的编程技术,可以让我们在这种情况下使用(x:xs) 模式。
【解决方案2】:

以向前计算的累积方式重写您的代码:

build [x] = go Leaf [x]
  where
  go tree (x:xs) = go (Node tree x Leaf) xs  -- build up ("accumulate") the tree 
  go tree []     = tree                      -- and reduce the list until empty

build [x,y] = go (Node Leaf x Leaf) [y]      -- can we use another, simpler 
  where                                      --   initial accumulator value?
  go tree (x:xs) = go (Node tree x Leaf) xs
  go tree []     = tree

....

你现在能简化一下吗?你注意到这里的相似之处了吗?可以使用任意长度的列表调用build,即匹配(x:xs) 模式吗?

【讨论】:

    【解决方案3】:

    您需要递归地编写此函数,方法是找到一种方法,使一个复杂的情况可以用一个更简单的情况来解决。因为您要进行模式匹配的是一个列表,所以您将使用(x:xs) 模式将一个列表分解为它的头部和尾部。像这样的:

    build :: [LogMessage] -> MessageTree
    build [] = ... -- remember lists can be empty!
    build [x] = Node Leaf x Leaf
    build (x:xs) = -- something to do with build [x] and xs
    

    【讨论】:

      【解决方案4】:

      您可能喜欢x:xs 模式,它匹配任何(非空)列表,将x 绑定到第一个元素,将xs 绑定到其余元素。它通常与递归定义结合使用,以依次检查列表的每个元素,无论列表有多长。

      如果你想匹配比三个元素更长的列表,模式可以扩展为x:y:z:rest;在这种情况下,xyz 将绑定到列表的前三个元素,rest 将绑定到其余元素。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-08-20
        • 2017-11-13
        • 2021-05-22
        • 1970-01-01
        • 2017-07-09
        • 2016-04-26
        • 2016-09-03
        相关资源
        最近更新 更多