Irrefutable destructuring patterns

#1

Looking at https://blog.rust-lang.org/2019/02/28/Rust-1.33.0.html I noticed something interesting, “irrefutable destructuring patterns.” It occurred to me that I’ve had plenty of code where this would be useful.

case class Row(a: A, b: B)

def f(Row(a, b)) ...

def g((a, b): (Int, String)) ...
or maybe
def g((a: Int, b: String)) ...

vs

case class Row(a: A, b: B)

def f(r: Row) ... {
   val Row(a, b) = r
}

def g(tuple: (Int, String)) ... {
  val (a, b) = tuple
}

Something similar I’ve seen proposed (and maybe is in Scala 2.13, not sure) is such patterns in lambdas where we would otherwise have to use case, but this is in the position of method arguments rather than lambdas. It would save a line here and there.

3 Likes
#2

I’ve wanted this many times! Would be a nice addition IMO

#3

Thinking about this some more, it is like the @ part of a pattern match. So you could have

def f(row@Row(a, b))
#4

This is something I would use. Particularly since you can write:

val someFunc = { case ... }
val otherFunc = ((a, b)) =>

and it correctly does the pattern matching.

#5

See: Allow defs to be implemented from functions

#6

I was thinking about how you would find what the type of the argument would be. It’d have to be the left side of the unapply method.

object X0 {
  def unapply(x: Int): Option[Int] = if (x == 0) Some(x) else None
}

def f(X0(zero)): Int = zero

so the type of f would be f(Int): Int, but it could throw a MatchError if one were to call it with f(3). This seems ok because it would throw a MatchError anyway if I were to do val X0(zero) = 5.

What if unapply is overloaded? I suppose in that case you’d have to explicitly state the type.

#7

In Common Lisp this is called argument-destructuring. It seems to me that having automatically destructuring function arguments could only be a positive improvement. It would help programmers write less repetitive code, and this pattern occurs often. It is indeed a nice feature of the cloJure as I understand (not being a cloJure expert). It is also a feature of Common Lisp, but unfortunately in CL the feature is only available to macros, not to normal lambda functions (but as most things it can be added to CL lambda functions using macros).

#8

This seems pretty cool, however I think if patterns are to be promoted to def args, I would strongly suggest that only patterns that can be checked for exhaustion should be allowed - its quite clear that you opt-in to unsafety with patt-defs.

#9

Destructuring of structs in Rust is irrefutable because there’s no way to override the default destructuring semantics i.e. there’s no equivalent of CompanionObject.unapply. Rust’s destructuring relies exclusively on struct fields. Scala relies exclusively on unapply method and because this method is opaque to the compiler, no destructuring is irrefutable (unless we’re talking about types from standard library which could be assumed to be unchanging).

1 Like
#10

Even though in Scala the situation is messier than Rust, we could still benefit from the idea, and code using it would be no more unsafe than it already is.

Final case classes can perhaps be irrefutable.