2

I am computing the Lp distance functions for non-negative p's. For all but p = 0 and p = ∞ a built-in pow() function serves well. Before I learned about a structural pattern matching, I had used a dictionary and exception handling:

from math import sqrt, inf
distance_function = {   0.0: lambda x, y: int(x != 0.0) + int(y != 0.0),
                        1.0: lambda x, y: abs(x) + abs(y), # Likely a tad faster than 'pow()'       
                        inf: lambda x, y: max(abs(x), abs(y))}                  
def lp_distance(x, y, p): 
    try:                return distance_function[p](x, y)                   
    except KeyError:    return pow(pow(abs(x), p) + pow(abs(y), p), 1.0/p)

Some people didn't want exceptions here. So I rewrote the snippet into the following one:

def lp_distance(x, y, p):
    match p:
        case 0.0:           return int(x != 0.0) + int(y != 0.0)
        case 1.0:           return abs(x) + abs(y)      
        # The line below triggers "SyntaxError: name capture 'inf' makes remaining patterns unreachable"
        case inf:           return max(abs(x), abs(y))
        # But the following works:
        case p if p == inf: return max(abs(x), abs(y))
        case _:             return pow(pow(abs(x), p) + pow(abs(y), p), 1.0/p)

Why case inf: is not correct (Python v3.10.2)?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • 1
    My guess from working on scientific computing before is that `inf`, `-inf` and `NaN` actually follow IEEE rules (I think 754 specifically). And according to those rules, `inf` is only closer to themselves. In this case, I think all patterns are `OR`ed and probably that is causing just using `inf` to result into `inf` always and hence unreachable for other patterns. If you do `1 and p == inf` instead of `p if p == inf`, I think it should work as well. I don't have `3.10` atm, will try this myself tonight. –  Feb 12 '22 at 13:22

2 Answers2

3

In a case statement, a simple name is a pattern that captures (assigns) to that name. In contrast, a dotted name is a patterns that refers to the value of that name.

In simple terms NAME will always succeed and it will set NAME = <subject>.

In simple terms NAME1.NAME2 will succeed only if <subject> == NAME1.NAME2

Using just case inf: means that the value to match is unconditionally assigned to the name inf – it does not matter if the name was previously bound.
What you want instead is case math.inf:, which means to compare against this value.

import math

def lp_distance(x, y, p):
    match p:
        case 0.0:
            return int(x != 0.0) + int(y != 0.0)
        case 1.0:
            return abs(x) + abs(y)      
        # compare against a value by using its dotted name
        case math.inf:
            return max(abs(x), abs(y))
        case _:
            return pow(pow(abs(x), p) + pow(abs(y), p), 1.0/p)
MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119
2

As noted by the other respondents, you can't use inf directly because that is a capture pattern. The obvious solution is to use a value pattern with a dotted lookup; however, that only works for positive infinity.

To handle other all special values like negative infinity and NaNs, you need guards:

match x:
    case 1.0: ...                   # Exact value (literal pattern)
    case 0.0: ...                   # Exact value (literal pattern)
    case _ if math.isfinite(x): ... # Normal cases
    case _ if math.isnan(x): ...    # NaNs defy equality tests
    case _: ...                     # Negative infinity   


   
Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485