I came across a pattern which I believe can be expressed more elegantly:
I have two functions f1,f2 :: Int -> Int (their impl. is not relevant), and a process :: Int -> Int which does the following:
- if
f1 xproducesx1different fromx, then repeat the process withx1 - otherwise, if
f2 xproducesx2different fromx, then repeat the process withx2 - finally, stop the process and return
x
My case ... of implementation is the following:
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'
which produces the following warnings:
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'
| ^^^^^^^^^^^^^^^^
Can anyone shed some light as to what patterns are overlapping, and how to implement process more elegantly?
There is no way to write a pattern for "a value equal to the one I have stored in a variable
x". This is because pattern matching is the primary way variables are created in Haskell.Here
xis a pattern. It's a very simple pattern, since it just matches any possible value for the argument toprocess, but you could have written a more structured pattern, or even multiple equations forprocessmatching different patterns for that argument. And within the scope of that pattern match (the entire RHS ofprocess), you havexas a local variable referring to the matched value.Here
xis once again a pattern, and once again it is a very simple pattern matching any possible value that was inspected by thecaseexpression. Then you havexas a new local variable referring to the matched value within the scope of the match (everything the RHS of the->arrow); and because you have created two local variables with the same namex, the most local one shadows the other in the scope where they both apply (so you have no way of referring to the originalxin the RHS of the->arrow, only the newxthat is the result offapplied to the originalx).If you think the pattern
xin the case expression ought to mean "match a value equal tox", then why should the patternxin the function argument mean "match anything and call itx"? You can't have your cake and eat it too1.Haskell makes the rule very simple: a variable appearing in a pattern is always the creation of a new variable to refer to the value that matched the pattern. It is never a reference to an existing variable to check if the matched value is equal to it. Only constructors will be "checked to see if they match"; variables are just bound to whatever is there2.
The same applies to your inner
case, where you meant to test the result to see if it was stillxbut actually just created yet anotherxshadowing both of the outerxvariables.This is why the compiler complains about your other pattern matches being redundant. Pattern are checked in order, and the first pattern in each
casealready matches anything (and calls itx), so the second match in eachcasewill never even be tried.So, since pattern matching can never test whether a value is equal to a variable, you just need to use a construct other than pattern matching!
if ... then ... else ...would work fine. You could also use a guard on a pattern.2 At least not if you want to be able to tell what a pattern means locally, without examining all containing scopes including the entire module and all imports. A hypothetical language could decide the meaning of the pattern based on whether there's already a variable of that name in scope, but I think Haskell makes right call here. Unexpected shadowing sometimes causes tricky bugs, but at least some sign of them is always local. It would be a nightmare if you could change a pattern from a catch-all to an equality check by introducing a global scope variable with the same name (possibly not even in the same module or even package!).
2 This is actually the core reason we have the syntactic distinction between constructors starting with a capital letter and variables starting with a lowercase letter! The language designers wanted it to be easy to tell at a glance which words are constructors to be matched and which are variables to be bound, without having to consider all the constructor names in scope.