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

Express a "case ... of" pattern more elegantly in Haskell

我遇到了一个我认为可以更优雅地表达的模式:

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

我的 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'

产生以下警告:

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'
   |         ^^^^^^^^^^^^^^^^

任何人都可以阐明哪些模式重叠,以及如何更优雅地实施 process 吗?

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

创建 变量的主要方式
process :: Int -> Int
process x =

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

    case f1 x of
        x ->

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

如果您认为 case 表达式中的模式 x 应该表示“匹配等于 x 的值”,那么为什么函数参数中的模式 x 表示“匹配任何东西并将其命名为 x”?你不能既吃蛋糕又吃蛋糕1.

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

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

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

因此,由于模式匹配 永远不会 测试一个值是否等于一个变量,您只需要使用模式匹配以外的构造! if ... then ... else ... 可以正常工作。您也可以在图案上使用保护装置。


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


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

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

process :: Int -> Int
process x
    | x /= x1 = process x1
    | x /= x2 = process x2
    | otherwise = x
  where
    x1 = f1 x
    x2 = f2 x