Search code examples
pythonstructural-pattern-matching

Can you specify a default value for an optional element in structural pattern matching?


I have a class Expr that can contain an array of slugs, the first slug defines a type of operation, and in 1 case it can be followed by either 1 or 2 more slugs. Is there a way to use structural pattern matching in such a way that the last slug gets a default value when it's not given? Here are some close-ish attempts:

class Expr:
  def __init__(self):
    self.slugs = ["flows", "step1"]

e = Expr()

Solution 1:

This one works but requires me to add validation logic and duplicates the wildcard error logic, which I could otherwise fall back on:

match e:
  case Expr(slugs=["flows", step, *output]):
    if len(output) > 1:
      raise Exception("Invalid expression")
    output = output[0] if output else None
  case _:
    raise Exception("Invalid expression")

Solution 2:

This one doesn't work because one branch doesn't bind the output (SyntaxError: alternative patterns bind different names):

match e:
  case Expr(slugs=["flows", step]) | Expr(slugs=["flows", step, output]):

Solution 3:

This one would be my ideal (SyntaxError: invalid syntax):

match e:
  case Expr(slugs=["flows", step, output = None]):

I know solution 1 sort of does the job and it's a nitpick, but is it possible to get to a solution where the default value of the output variable can be bound within the case statement?


Solution

  • One approach is to use a guard after your Expr match statement. The guard can check if output is empty, in which case an assignment expression simply sets output to None, or if the length of output is less than 2. If either of those conditions are True, then control passes to the body of the first case statement. If not, then your default case _ is entered and the exception is raised:

    match e:
      case Expr(slugs=["flows", step, *output]) if (output:=output or None) is None or len(output) < 2:
        print(output)
      case _:
       raise Exception("Invalid expression")
    

    Usage examples:

    >>> class Expr:
    ...   def __init__(self, slugs):
    ...     self.slugs = slugs
    ... 
    >>> e = Expr(["flows", "step1", "step2"])
    >>> match e:
    ...   case Expr(slugs=["flows", step, *output]) if (output:=output or None) is None or len(output) < 2:
    ...     print(output)
    ...   case _:
    ...     raise Exception("Invalid expression")
    ... 
    ['step2']
    
    >>> e = Expr(["flows", "step1"])
    >>> match e:
    ...   case Expr(slugs=["flows", step, *output]) if (output:=output or None) is None or len(output) < 2:
    ...     print(output)
    ...   case _:
    ...    raise Exception("Invalid expression")
    ... 
    None
    
    >>> e = Expr(["flows", "step1", "step2", "step3"])
    >>> match e:
    ...   case Expr(slugs=["flows", step, *output]) if (output:=output or None) is None or len(output) < 2:
    ...     print(output)
    ...   case _:
    ...    raise Exception("Invalid expression")
    ... 
    Traceback (most recent call last):
      File "<stdin>", line 5, in <module>
    Exception: Invalid expression