1

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

我有两个功能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'

这会产生以下警告:

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更优雅地实现?

4

2 回答 2

5

按照Ben的建议,我写了以下内容:

process :: Int -> Int
process x
    | x /= x1 = process x1
    | x /= x2 = process x2
    | otherwise = x
  where
    x1 = f1 x
    x2 = f2 x
于 2021-12-19T22:24:35.987 回答
5

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

process :: Int -> Int
process x =

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

    case f1 x of
        x ->

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

如果你认为xcase 表达式中的模式应该意味着“匹配一个等于x”的值,那么为什么x函数参数中的模式应该意味着“匹配任何东西并调用它x”?你不能有你的蛋糕,也吃它1

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

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

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

所以,由于模式匹配永远无法测试一个值是否等于一个变量,你只需要使用模式匹配以外的构造!if ... then ... else ...可以正常工作。您也可以在模式上使用防护。


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


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

于 2021-12-19T21:56:34.063 回答