在 Haskell 中更优雅地表达“case ... of”模式

Ale*_*inu 1 haskell pattern-matching fixed-point-iteration

我发现了一种我认为可以更优雅地表达的模式:

我有两个函数f1,f2 :: Int -> Int(它们的实现不相关),以及一个process :: Int -> Int执行以下操作的函数:

  • 如果f1 x产生的结果x1与 不同x,则重复该过程x1
  • 否则,如果f2 x产生的结果x2与 不同x,则重复该过程x2
  • 最后,停止进程并返回x

我的case ... of实现如下:

f1 :: Int -> Int
f1 = undefined

f2 :: Int -> Int
f2 = undefined

process :: Int -> Int
process x =
    case f1 x of
        x ->
            case f2 x of
                x -> x
                x' -> process x'
        x' -> process x'
Run Code Online (Sandbox Code Playgroud)

这会产生以下警告:

so.hs:13:17: warning: [-Woverlapping-patterns]
    Pattern match is redundant
    In a case alternative: x' -> ...
   |
13 |                 x' -> process x'
   |                 ^^^^^^^^^^^^^^^^

so.hs:14:9: warning: [-Woverlapping-patterns]
    Pattern match is redundant
    In a case alternative: x' -> ...
   |
14 |         x' -> process x'
   |         ^^^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

谁能阐明哪些模式是重叠的,以及如何process更优雅地实现?

Ben*_*Ben 6

无法为“等于我存储在变量中的值x”编写模式。这是因为模式匹配是 Haskell 中创建变量的主要方式。

process :: Int -> Int
process x =
Run Code Online (Sandbox Code Playgroud)

x是一个模式。这是一个非常简单的模式,因为它只匹配 参数的任何可能值process,但您可以编写一个更结构化的模式,甚至可以编写多个方程来process匹配该参数的不同模式。在该模式匹配的范围内( 的整个 RHS process),您可以将x其作为引用匹配值的局部变量。

    case f1 x of
        x ->
Run Code Online (Sandbox Code Playgroud)

x又是一个模式,而且它又是一个非常简单的模式,匹配表达式检查的任何可能的值case。然后你有x一个新的局部变量,引用匹配范围内的匹配值(箭头的右侧的所有内容->);并且因为您创建了两个具有相同名称的局部变量x,所以最局部的变量在它们都适用的范围内隐藏另一个(因此您无法x在箭头的右侧引用原始变量->,只能引用新的变量xf应用于原始的结果x)。

如果您认为 case 表达式中的模式x应该表示“匹配等于”的值,那么为什么函数参数中的x模式应该表示“匹配任何内容并调用它”?鱼与熊掌不可兼得1xx

Haskell 使规则变得非常简单:模式中出现的变量总是创建一个新变量来引用与模式匹配的值。它绝不是对现有变量的引用来检查匹配值是否等于它。只有构造函数才会被“检查它们是否匹配”;变量只与存在的任何东西绑定2

这同样适用于您的 inner case,您打算测试结果以查看它是否仍然存在,x但实际上只是创建了另一个x遮蔽两个外部x变量的结果。

这就是编译器抱怨您的其他模式匹配多余的原因。模式按顺序检查,每个模式中的第一个模式case已经匹配任何内容(并调用它x),因此每个模式中的第二个匹配case永远不会被尝试。

因此,由于模式匹配永远无法测试一个值是否等于一个变量,因此您只需要使用模式匹配之外的构造即可!if ... then ... else ...会工作得很好。您还可以在图案上使用防护装置。


2至少如果您希望能够在本地了解模式的含义,而不检查所有包含范围(包括整个模块和所有导入),则至少不需要。假设的语言可以根据范围内是否已经存在该名称的变量来决定模式的含义,但我认为 Haskell 在这里做出了正确的选择。意外的阴影有时会导致棘手的错误,但至少其中一些迹象始终是本地的。如果您可以通过引入具有相同名称的全局范围变量(甚至可能不在同一模块甚至包中!)来将模式从包罗万象更改为相等检查,那将是一场噩梦。


2这实际上是我们区分大写字母开头的构造函数和小写字母开头的变量的语法区别的核心原因!语言设计者希望一眼就能看出哪些单词是要匹配的构造函数,哪些是要绑定的变量,而不必考虑范围内的所有构造函数名称。

  • 您*可以*使用“ViewPatterns”语言选项以及模式“((== x) -> True)”来表达“一个等于我存储在变量“x”中的值”,但通常不是值得做,因为编写为防护或“if”要简单得多。 (2认同)

Ale*_*inu 5

根据Ben的建议,我写了以下内容:

process :: Int -> Int
process x
    | x /= x1 = process x1
    | x /= x2 = process x2
    | otherwise = x
  where
    x1 = f1 x
    x2 = f2 x
Run Code Online (Sandbox Code Playgroud)