JEP 325: Switch Expressions
openjdk.java.net
openjdk.java.net
Clojure macros have spoiled me with cond, condp and case plus whatever else I or the community come up with. And my half thought out changes are nicely isolated to a single project or library instead of baked into the language for eternity.
I.e. the Java boolean type, which can only be true or false (and not default). Could it be that there are other valuable uses of this ability, instead of using enums where you always have to handle the default case?
Can it really be the case that true/false is the only case where not having to worry about default is extremely helpful?
I am also a little unsure what you mean by not worrying about default. Even polymorphism has to deal with this, if you call a polymorphic method on an object that doesn't implement that interface you'll get at best a compile problem, at worst a cast exception. This is why some languages provide support for a "default" or "method not implemented" hook in polymorphism.
If you are arguing that polymorphism is a better way to build systems than switch/case statements then no argument here. Open constructs like polymorphism are far better for growing and evolving large systems over time.
That doesn't mean we should exclude a nice way to say "i've got a variable that i want to translate to some other value in a few different ways".
My point was only that it just seems so silly to me that everyone is discussing whether this syntax is "best" or how it "doesn't do X, Y or Z", because I remember doing that too before Rich Hickey (angelic choir noises) showed me that actually you don't have to put up with that shit. Just use a language that puts syntax extension in the hands of it's community and let them figure it out without burdening the core language with a thousand little tradeoffs for the 90% use case.
The downside of that is that some Lisp projects can only be understood by the original authors, during the time they were actually active on it.
I have seen a few that went DSL crazy with macros.
Scalaz is another "good" example of that.
Within a function, each switch branch can be a return. Then you get the benefits:
* Make the original function shorter.
* Give a descriptive name to what you're computing.
* Make the computation reusable.
It's what I find lacking with lambdas: it makes it too easy to forego good refactoring because it makes too easy to write one-off code that in reality will be rewritten many times in different places.
If someone is writing the same lambda n times instead of extracting it in a common method the problem is not the lambda function, but the developer. Closures are probably the best feature in Java and they have been delayed enough.
I don't think that's what is being suggested. I think the suggestion is to move the entire switch statement to a separate function, use 'return' in the switch cases, and have the caller assign the variable once, from the return value of the function.
The footgun of 'break' in switch statements has always bothered me, I strongly prefer the kotlin/swift way of doing this.
Anyway, Lambda expressions have been around in, like, Lisp for at least 40 years and in mathematics for, what, 80? So everyone's slow by that account.
C# is much faster and has much more features, but because of that it's also a bit of a mess sometimes. For example regarding closures/anonymous functions it has delegates, lambdas and anonymous (inner) classes which all have overlap but aren't fully the same.
Java only has anonymous inner classes, and a convenient syntax and good support for anonymous inner classes with only 1 method (which they call lamdbas)
The only variant of Java that desugars lambas into anonymous inner classes is Android, because Google.
Java has had closures since 1997 hasn’t it? Don’t anonymous classes close over local state? Just the syntax was verbose.
Do you mean the lambda syntax? Well that isn’t thue only thing in Java that forms a closure.
‘Closure’ and ‘anonymous function syntax’ are orthogonal.
I think that's reasonable though, capturing mutable state is a recipe for disaster, and if you really want/need to you can wrap it in a properly synchronized container (e.g. AtomicReference in Java)
Apart from effective finality and implementation, the semantics of anonymous classes and lambdas is the same!
Pascal, and presumably Algol, had no break - following the guard condition was “a statement” (either just a single one, or an explicitly bounded block / compound statement)
Nice to see plans to act something like Lisp’s “(COND ...)” or Ruby’s case statement (et al).
And in rare cases where you do want fallthrough, they added the ability to "goto case" from the body of one case label to another. Which is kinda nice, because it follows the original C design in treating case labels as, well, labels.
Thankfully this is changing now, with Pascal and ML syntax becoming again fashionable.
Lisp introduced 'cond' when? Also, pattern matching in it's slightly more advanced form is as old as ML.
It's good to see languages finally admitting that expressions > statements. Still waiting for them to implement proper tail recursion though so I can replace statement based loops with expression based ones.
The kotlin/swift way of declaring this flow does not let you do this mistake.
It might take quite a lot of work to fix (or bypass) all the reported problems in a large codebase but it is worth it.
C# has seen a similar push to implement pattern matching, down to the use of `_` as discard. The first iteration of this feature in C# 7 is feature light, but it's a good start, and they've promised more to come. The team behind C# has become really good at getting over NIH and embracing new technologies and techniques while somehow preserving backwards compatibility and ergonomics. Anders Hejlsberg doesn't kid around.
Here's an example cribbed from an official MSFT blog [0]:
public static int Count<T>(this IEnumerable<T> e)
{
switch (e)
{
case ICollection<T> c: return c.Count;
case IReadOnlyCollection<T> c: return c.Count;
// Matches concurrent collections
case IProducerConsumerCollection<T> pc: return pc.Count;
// Matches if e is not null
case IEnumerable<T> _: return e.Count();
// Default case is handled when e is null
default: return 0;
}
}
A lot of people have lamented this as the death of F#, but I think MSFT is making the right calls here.EDIT:
Little-known fact: C# actually does have discriminated unions, and has had for a long time (from the start?) in the form of the `Exception` base class. In fact, pattern matching in C# can be "pulled off" (read: hacked together as a proof-of-concept) without the changes from C# 7 by simply using the extended catch syntax:
class MyType1 : System.Exception
{ /* ... */ }
class MyType2 : System.Exception
{ /* ... */ }
void foo()
{
throw something ? new MyType1() : new MyType2();
}
void bar()
{
try
{
foo()
}
catch (MyType1 mtype)
{
//take type-specific action here
}
catch (MyType2 mtype)
{
//take type-specific action here
}
catch (Exception _)
{
//the _ was optional, but this is the `default` block
}
}
[0]: https://blogs.msdn.microsoft.com/seteplia/2017/10/16/dissect... def showNotification(notification: Notification): String = {
notification match {
case Email(email, title, _) =>
s"You got an email from $email with title: $title"
case SMS(number, message) =>
s"You got an SMS from $number! Message: $message"
case VoiceRecording(name, link) =>
s"you received a Voice Recording from $name! Click the link to hear it: $link"
}
}Rust is not the source of all innovations.
> Anders Hejlsberg doesn't kid around.
Except he doesn't do much C# nowadays, Mads Torgersen is the actual C# chief architect.
While Anders still advises on C#, he has switched focus to Typescript.
(And tuples, ADTs ah the list goes on..)
Concise AND expressions (eg "case UP, DOWN -> 1;"): +1
Using keyword 'break' instead of 'return': -1
You could do something context sensitive to make 'return' act differently in a switch expression versus a switch statement, but that seems more confusing.
But you shouldn't. That would break existing code. What if you actually want to return something from within a switch statement?
But it's makes language more complicated and what is more important - it makes harder to refactor, especially via IDE. So my opinition: we don't need this and this doesn't have any value besides marketing.
Type-based function overloads are not specific to OOP, by the way.
The -> is not a good idea and break <something> should be return <something>. The -> throw e; "sugar" is just ridiculous!
let rec fib = fun x ->
match x with
| 0 -> 1
| 1 -> 1
| _ -> fib (x - 1) + fib (x - 2)it's just unnecessary. (but they probably do it for backwards compat or so, no idea..)
Better appreciate it for that, than wishing for features that will never come.
Also it's much more similar to Scala's "match" expression, which is also on the JVM and predates Kotlin by about 7 years.
Here, “int result = switch...” seems nice for that one situation, “until it isn’t” (and in my experience these “until” events can come as soon as the very next time you have to maintain the thing). Oh, wait: I actually want to log something in one of the cases in addition to the assignment but since the entire thing is a fancy assignment expression now I have to rewrite 90% of it to add a glorified print statement for one of them!?
And it’s not just Java. In “new” C++ for instance, the highly-specialized syntax for loop iteration is nice but it has the same problem: sometimes the change you’re making does need iterators, unusual increment points, etc. and you end up peeling apart everything that was supposed to be so helpful and rewriting way more than you should need to.
You can use logging/side effect in it. You can not assign it to a variable, all valid in Java.
Consider the if expression (ternary ?: operator in Java). In most modern languages (e.g. Scala, Kotlin, Rust) there is no ternary operator, and only an if-expression:
int value = if(something) 1 else 2
According to you, this is wrong, but it isn't, you can still use it as a statement: if(something) {
doThis()
} else {
doThat()
}
Just like you can have a statement (line) with just "1+1" or "doThis()", you can have a statement of just an if-expression.And you can also add side-effect (logging) withing expressions:
int value = if(something) {
log("Uh oh");
-1
} else {
1
}
Making a language construct an expression is the complete opposite of tying it to a particular use-case, it's giving it the ultimate flexibility because you can use expressions everywhere.And there are very simple examples of that problem, right in the article:
1. Allowing the entire switch expression to bind to the same assignment makes it difficult to maintain when adding a case that will not perform that assignment. Now, instead of adding one line, you’re tearing apart half the block and trying to find a way to make equivalent new code.
2. Even adding a single line to a case may be impossible without rewriting the case. Given some 'case "Bar" -> 2;', the maintainer has to redo the entire thing: rewrite it as 'case "Bar":' with a colon, add 'break 2;' to preserve original behavior, and then add their stuff. That’s just broken; it won’t even produce a clean "diff" because you’re “changing” lines that don’t have anything to do with your new code. And you can bet at some point that somebody may just plain forget to add back the "break" when rewriting the expression needlessly, introducing a bug.