-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Scala Wart: Conflating total destructuring with partial pattern-matching #2578
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Thanks for all the issues! Very useful to have them here. We are currently super-busy preparing for Scala Days Copenhagen, but will get back to them, hopefully soon after. Of course if people want to jump in discussing them now, please do so! |
This turned up in the scala/contributors gitter scala> val 2 = 1
scala.MatchError: 1 (of class java.lang.Integer)
... 32 elided
|
The canonical form is |
In my opinion, destructuring assignment should always be irrefutable. The compiler knows, or at least could know, that many things users try to do are type unsafe and cannot possibly succeed at runtime. This leads to bugs (some obvious, some subtle) and is just a generally confusing part of the language. |
I like the idea to use for (case (a, b) <- xs) ...
for (case x :: xs <- xss)
val case (a, b) = x
val case x :: xs = ys As opposed to case val (a, b) = x
case val x :: xs = ys
|
Or maybe |
That would be confusing on the right hand sides of match expressions |
Right. In fact I think that having for (case (a, b) <- xs) ...
for (case x :: xs <- xss)
val (a, b) @unchecked = x
val x :: xs @unchecked = ys The |
I like that, too. |
To avoid breaking code that relies on the current behavior (the filtering behavior of So why not make exhaustiveness checking always strict by default, while we're at it? What's the rationale for warning in this case: scala> def f: List[Int] => Int = { case Nil => 0 }
1 |def f: List[Int] => Int = { case Nil => 0 }
| ^
| match may not be exhaustive.
|
| It would fail on pattern case: List(_, _: _*) ...but not warning in that case?: scala> def f: Seq[Int] => Int = { case Nil => 0 }
def f: Seq[Int] => Int I've seen users confused by the following code failing at runtime – an honest mistake IMHO, confusing def f: Seq[Int] => Int = { case Nil => 0 case x :: xs => x } To get rid of the warning, users who know better could use a special def fallible[A,B](f: PartialFunction[A,B]): (A => B) = f
// (The very fact we can write the abvove function without warnings, because
// PartialFunction[A,B] <: (A => B), seems itself broken, by the way.) To be used as: scala> def f: Seq[Int] => Int = fallible{ case Nil => 0 case x :: xs => x }
def f: Seq[Int] => Int |
@LPTK I don't think we can currently rely on the exhaustiveness checker for either of those cases. Irrefutability has to be established separately, but it should be much simpler for a single case. Upgrading the exhaustiveness checker to detect all possible violations would be a long term project. We'd have to find someone to do this first. I like the idea of |
Fix #2578: (part 2) Make for-generators filter only if prefixed with `case`.
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:
The basic problem here is that when Scala sees
val (a, b, c) = ...
, it doesn'tmean which of two things you mean:
...
, and help me check that it's a tuple...
, and fail at runtime if it is not a tupleCurrently, 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
:Though the following also fails with an exception:
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 alreadyassociate with partial functions and pattern matches:
This would indicate that you want to perform an "partial fail at runtime"
match, and the earlier non-
case
examples: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, andwhile
-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.htmlHere's a 2.12 failure mode in for-comprehensions that's nonsensical, and is fundamentally caused by this issue:
The text was updated successfully, but these errors were encountered: