Idea: throw try away and unify match-catch

match and catch clauses are similar: both contain a partial function.
Yet catch is part of a more complicated construct: try-catch-finally.

I propose to

  • uncouple catch and finally
  • remove the try keyword
  • create a match-catch construct where either one of the two clauses is optional

This would simplify syntax and increase expressiveness.

Rationale

From the Scala Language Specification Version 2.9:

A try expression try { b } catch e1 finally e2
is a shorthand for try { try { b } catch e1 } finally e2

The shorthand is baroque and may be removed. Then it would be possible drop the try keword, before both catch and finally.

e1 finally e2
e1 catch e2

Now the catch construct became much like the match:

e1 match e2
e1 catch e2

It could even be possible to combine the match/catch expression:

e1 match e2 catch e3

e3 would then handle exceptions occurring in e1, but not those in e2.

So when e1 has ended the execution selects between a success branch and a failure branch, depending on whether an exception had occurred.

There is currently not an easy way in Scala to specify such branching on general expressions; yet it would be similar to if-then-else, and to the onComplete method of futures.

The try keyword is very important, as it clearly marks the beginning of a protected section where exceptions will flow differently. Removing it will make it much harder to reason about actual programs. And that’s irrespective of any potential simplification.

Also, the thing after the try is very often a block. Nobody wants to write

{
  val x = foo()
  bar(y)
} catch ...

Without the initial try it is totally unreadable. It gets even worse when you realize that you often need a blank line before a bare block like that.

The same does not apply to match because the thing on the left is typically a short expression. And if it’s not short, you can extract it in a val first. You cannot do that with the try block because the block would not be protected by the try anymore.

5 Likes

This is really surprising behavior. This would be a break with how dropped parens are handled, which seems ill-advised in the single most dangerous language construct in Scala.

It seems really counter to how Scala usually binds expressions for e1 match e2 catch e3 to be equivalent to this:

(try e1 catch e3) match e2
2 Likes

Amazing proposal! If anything can throw anything, what’s the point of try?

“There is no try,” as I think someone has already joked.

To sjrd’s point, you can always wrap the throwing expr in locally. To give it a name.

I still need to study the other response about “the single most dangerous language construct in Scala”, but I do sense the need for a t-shirt with that slogan. Maybe just with Martin Odersky’s profile photo?

2 Likes

Just to be pedantic, you could extract the try block to a def. I think it would behave basically the same (except for an extra frame in the stack trace).

I agree that having try before makes it a lot easier for people reading the code.

1 Like

Extracting the block to a def seems like it would result in having a chunk of code which is known to throw exceptions, divorced from any indication that the programmer who originally wrote it understood this.

Probably just me, but that seems like a bad idea

Agreed. I was just pointing out that technically it’s possible like with match albeit not with a val. Definitely not recommended.

1 Like

Note that using quiet control syntax and optional braces you can already write this today:

  try readList() match
    case Nil => 1
    case x :: xs => 2
  catch
    case java.io.IOException => 3
    case java.lang.Error => 4

I don’t really think this needs improving!

3 Likes

Thank you Sebastian; you make a good case for the try keyword.

Sebastien’s case (and I apologize that I’m too American to do the accent without cutting and pasting) is that we can’t tell what is an expression.

We should be doing more of e.ensuring(f) and e.tap(f) and therefore also e match pf and e catch pf.

Maybe we also make match and therefore catch just members of Any. e.catch { pf }.

The fact that we are brainwashed by try syntax is no reason to retain it.

The open question is whether to keep Try(e) or prefer e.try.

Let’s recall that part of the brainwashing is that it was so cool that f { body } looks like built-in syntax.

That is still cool, but we don’t care about that. For “universal” stuff such as “I’d like to catch exceptions here”, the suffix notation is totally fine. The expression ought to be small. e.try instead of

locally { e }.try.

Returning to SĂ©bastien’s example, and I hope I got the accent right, my French friend is so rigid in that regard, and strangely his spouse is for disposing of the diacritics, which I actually think lends that very French frisson to the relationship, I never remember if 42 + if (true) 0 else 27 works, which came up in a recent PR about expressions. So we don’t actually know what is a (simple) expression. And that friction matters.

I haven’t studied whether Odersky syntax, which we may now call quiet control syntax with optional braces, is amenable to the suffix and infix notations. I think it’s appropriate to call it Odersky syntax because Dr Odersky exudes a vibe of quiet control.

2 Likes

I see your point.

Yet a construct that combines match and catch is missing from the language. A clearer construct with the existing keyword with would be

e1 match e2 with catch e3

So this match-with-catch would work like a future’s onComplete method.

Thinking along this line: using the keyword yield here would yield more idiomatic alternatives for the methods onComplete, onSuccess and onFailure of futures:

f yield match e
f yield catch e
f yield match e1 with catch e2

The compiler would translate these to method calls: yield_match, yield_catch and yield_match_with_catch. The recipient should be of a type Yield_match_catch_handler or so.

For a future f as a recipient of yield match etc, an implicit conversion should be in scope that turns f into a Yield_match_catch_handler with methods yield_match, yield_catch and yield_match_with_catch calling the future’s onSuccess and onFailure methods.

I’d recommend avoiding the term “brainwashed” as it is a heavily loaded term in English.

Please keep in mind these are not equivalent. Among other differences, Try will only handle non-fatal exceptions.

I don’t really think that it is. Even without quiet control syntax, this is valid Scala 2 code:

try readList() match {
  case Nil     => 1
  case x :: xs => 2
} catch {
  case _: java.io.IOException => 3
  case _: java.lang.Error     => 4
}

This would be problematic, because (as implied above by the note about try/Try non-equivalence) the current behavior of try and Future’s error handling are not equivalent.

2 Likes

Can we not have this absurd language policing?

If it’s really true that someone reading this can have their PTSD triggered by a commonly used word, they absolutely have my sympathies, but it is not a tenable policy to avoid perfectly commonplace language. For one thing, that sure is one slippery slope. Pretty soon you have nothing left but Newspeak.

If people do want to adopt such a policy then define a principle so that it can be applied consistently. I expect you’ll have a hard time coming up with one that forbids “brainwashed” and doesn’t take half of everything else with it.

3 Likes

Dude, chill. It was advice.

Someone says “you’ve been brainwashed by X”, and I stop listening to them, because they are clearly conveying the idea that they don’t believe I am capable of rational thought on the subject.

There’s a world of difference between, “Hey mods, this guy’s using Forbidden Language”, and “I’d recommend avoiding this loaded term”.

“Don’t insult the people you’re trying to convince” is pretty much Communication 101

2 Likes

I apologize if you feel I insulted you.

That said I understand that you aren’t demanding, only offering advice. My point was that it’s poor advice.

Now there’s a third thing you could say (besides for demanding or advising), which you alluded to now, namely requesting. If you would say, “please don’t use the word brainwash, it interferes with my ability to focus” or something like that, I have no problem with that of course.

But it was certainly not intended to imply you were literally brainwashed and can’t think rationally. If you read more if Som Snytt you’ll see that his writings are about 70% humor and wordplay. Also note that he said “we,” including himself (and most people). There’s something humorous about thinking about how constrained our thoughts really are, and about using exaggerated terms like “brainwashed” to wonder about the extent of our own biases.

1 Like

<meta>

nafg, I think Morgen wasn’t making a comment about about his mental state. It seems to me that he was saying, “When you use ‘brainwashed’, it indicates a lack of respect for the person you are addressing, as well as suggesting that you don’t intend to evaluate their arguments in good faith as you believe they are operating as if due to intentionally misleading psychological conditioning, not rational evaluation.” (Morgen–it wasn’t very clear the first time because “heavily loaded term” isn’t explanation enough! Indeed, “loaded term” is kind of a loaded term given its pejorative connotations. Best to avoid it in most cases and instead say what the problem is, don’t you think? nafg–nonetheless, your response the first time was kinda over-the-top!)

Morgen, nafg’s point about understanding the speaker a little better is very apropos–Som says all sorts of peculiar things that, taken together, add up to a quite amusing and often insightful whole; as with many things, if you cut out individual bits they can seem, isolated, a lot worse than they are.

</meta>

I prefer function-call-try to postfix-try because I tend to read code from top to bottom, and understanding what one needs to think about w.r.t. exceptions is information you want to have in advance. Hence, try should appear at the beginning to aid with documentation/code understanding.

We already have Try or try at the beginning, so I think everything’s good as it stands.

5 Likes

I understand that and it would be true if Som had said “you only think that because you are just brainwashed.” But in fact he said “we are brainwashed.” Even if it was meant literally (and it should be obvious it was not) it’s not condescension when you include yourself. (It can be un-generous though.)

1 Like

I don’t really think that it is. Even without quiet control syntax, this is valid Scala 2 code:

Valid Scala code indeed, but it is not doing what I had in mind for match-with-catch:

e1 match e2 with catch e3

e3 would handle exceptions occurring in e1 , but not those in e2 .
This way

  • e2 handles the successful results of e1
  • e3 handles failures of e1

That is like the branching flow in if statements and in a future’s onComplete method.

This would be problematic, because (as implied above by the note about try / Try non-equivalence) the current behavior of try and Future 's error handling are not equivalent.

I don’t see how that would problematic in practice. I guess there would be a difference when an Error is thrown during the future’s execution. What would be the difference between the match-with-catch version and the onComplete version?

Inverting the example is pretty trivial:

try readList() catch {
  case _: java.io.IOException => 3
  case _: java.lang.Error     => 4
} match {
  case Nil     => 1
  case x :: xs => 2
} 

It’d be problematic because the Future is asynchronous, so if there’s a try wrapping code which returns a Future, it’s because the failure can happen outside the Future context. By rewriting the try into the Future context, you’d lose the error handling put there to catch Exceptions which are known to be outside this context.

It’s not great when it happens, but there has to be a way to deal with situations where creating the Future can fail before the Future is initialized.

<meta>

That is exactly correct.

Fair point, I’ll take care to be more explicit in the future. “Brainwashed” is a complicated term, and I had hoped to avoid dealing with that complexity - a mistake on my part in retrospect.

@nafg, I’m aware of what @som-snytt said, and you’ll note that I ascribed no malice to him. Specifically, I described the chilling effect on discussion you get throwing around stuff like “brainwashed”, and was careful to use language that did not imply I was attempting to put that on his original usage.

To be plainly honest, I figured English was not his first language, and he was using a word he understood the definition of, but not the connotative baggage it carried. I wasn’t even annoyed with him, I wanted to help him avoid putting his foot in his mouth - because I do that so often it’s hard to get the taste out.

If you want to avoid offending me, don’t misrepresent what I say, because that did piss me off. Call me out if I say something stupid - because (Aspie that I am), sooner or later I will say something monumentally stupid, but stick to what I actually say. Lord knows you’ll have no shortage of material.

I think I’ve said my piece. Responses should probably go to DM, to avoid cluttering the thread further

</meta>