question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Scala Wart: Conflating total destructuring with partial pattern-matching

See original GitHub issue

Opening this issue, as suggested by Martin, to provide a place to discuss the individual warts brought up in the blog post Warts of the Scala Programming Language and the possibility of mitigating/fixing them in Dotty (and perhaps later in Scala 2.x). These are based on Scala 2.x behavior, which I understand Dotty follows closely, apologies in advance if it has already been fixed


The following example, also from Lincon Atkinson’s blog, compiles without warning and fails with an exception at runtime:

@ val (a, b, c) = if (true) "bar" else Some(10)
scala.MatchError: bar (of class java.lang.String)
  $sess.cmd105$.<init>(cmd105.sc:1)
  $sess.cmd105$.<clinit>(cmd105.sc:-1)

The basic problem here is that when Scala sees val (a, b, c) = ..., it doesn’t mean which of two things you mean:

  1. Help me extract the values from ..., and help me check that it’s a tuple
  2. Help me extract the values from ..., and fail at runtime if it is not a tuple

Currently, it assumes the latter, in all cases.

That makes any sort of “destructuring assignment” unchecked, and thus extremely unsafe.

The above example at least happily fails with an exception, but the following exhibits the same problem, but instead truncates your data silently, losing the 5:

@ for((a, b) <- Seq(1 -> 2, 3 -> 4, 5)) yield a + " " +  b
res107: Seq[String] = List("1 2", "3 4")

Though the following also fails with an exception:

@ Seq(1 -> 2, 3 -> 4, 5).map{case (a, b) => a + " " + b}
scala.MatchError: 5 (of class java.lang.Integer)
  $sess.cmd108$.$anonfun$res108$1(cmd108.sc:1)
  scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:234)
  scala.collection.immutable.List.foreach(List.scala:389)
  scala.collection.TraversableLike.map(TraversableLike.scala:234)
  scala.collection.TraversableLike.map$(TraversableLike.scala:227)
  scala.collection.immutable.List.map(List.scala:295)
  $sess.cmd108$.<init>(cmd108.sc:1)
  $sess.cmd108$.<clinit>(cmd108.sc:-1)

While interpretation #2 makes sense in match blocks and partial-functions, where you expect to “fall through” to the next handler if it doesn’t match, it doesn’t make much sense in cases like this where there is nowhere to fall through to.

The correct solution would look something like this:

  • By default, assume the user wants 1. “Help me extract the values from ..., and help me check that it’s a tuple”

  • Require a special keyword if the user wants 2. “Help me extract the values from ..., and fail at runtime if it is not a tuple”

A possible syntax might be using case, which Scala developers already associate with partial functions and pattern matches:

for(case (a, b) <- Seq(1 -> 2, 3 -> 4, 5)) yield a + " " +  b

case val (a, b, c) = if (true) "bar" else Some(10)

This would indicate that you want to perform an “partial fail at runtime” match, and the earlier non-case examples:

for((a, b) <- Seq(1 -> 2, 3 -> 4, 5)) yield a + " " +  b

val (a, b, c) = if (true) "bar" else Some(10)

Could then verify that the pattern match is complete, otherwise fail at compile time.

PostScript:

I noticed here that the Scala language spec already has words in it that talk about irrefutable patterns, which seem to match what I want these for-comprehension and val-destructuring cases to require. Whether those words mean anything, I do not actually know

Yawar Amin has noted that in the Rust language, the cases which closely mirror those in Scala (let destructuring, and while-loop/if destructuring) do have a requirement of the destructuring patterns being irrefutable https://doc.rust-lang.org/beta/book/second-edition/ch18-02-refutability.html

Here’s a 2.12 failure mode in for-comprehensions that’s nonsensical, and is fundamentally caused by this issue:

lihaoyi ~$ amm
Loading...
Welcome to the Ammonite Repl 0.9.7
(Scala 2.12.2 Java 1.8.0_112)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
@ for((a, b) <- Right((1, 2))) yield a + b
cmd0.sc:1: value withFilter is not a member of scala.util.Right[Nothing,(Int, Int)]
val res0 = for((a, b) <- Right((1, 2))) yield a + b
                              ^
cmd0.sc:1: type mismatch;
 found   : Any
 required: String
val res0 = for((a, b) <- Right((1, 2))) yield a + b
                                                  ^
Compilation Failed

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:28
  • Comments:12 (10 by maintainers)

github_iconTop GitHub Comments

3reactions
lihaoyicommented, Aug 12, 2017

This turned up in the scala/contributors gitter

scala> val 2 = 1
scala.MatchError: 1 (of class java.lang.Integer)
  ... 32 elided

2reactions
som-snyttcommented, May 30, 2018

The canonical form is val 1 = 2.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Scala Pattern Matching Warts and Improvements
pattern matching warts, which are clearly unjustified limitations and quirks; ... total from partial destructuring in for comprehensions.
Read more >
4. Pattern Matching - Programming Scala, 2nd Edition [Book]
With Scala's pattern matching, your cases can include types, wildcards, sequences, regular expressions, and even deep inspections of an object's state. This ...
Read more >
Scala Pattern Matching Warts and Improvements - Reddit
There has been a lot of talk about separating total from partial destructuring in for comprehensions. One of the proposed syntax was:
Read more >
Rust: A Scala Engineer's Perspective - BeachApe.
You get to decide whether to pass things by value or by reference as well as mutability of bindings (including when pattern matching)....
Read more >
Warts of the Scala Programming Language
... only implicit parameter lists; Presence of comments affects program logic; Conflating total destructuring with partial pattern-matching.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found