You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2021/07/18 13:50:33 UTC

[groovy-website] branch asf-site updated: Add switch expressions to Groovy 4 release notes.

This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-website.git


The following commit(s) were added to refs/heads/asf-site by this push:
     new 06ce5eb  Add switch expressions to Groovy 4 release notes.
06ce5eb is described below

commit 06ce5eba2b4b10c3f9d785a6042d07b2f5b7a580
Author: Paul King <pa...@asert.com.au>
AuthorDate: Sun Jul 18 23:50:27 2021 +1000

    Add switch expressions to Groovy 4 release notes.
---
 site/src/site/releasenotes/groovy-4.0.adoc | 157 +++++++++++++++++++++++++++++
 1 file changed, 157 insertions(+)

diff --git a/site/src/site/releasenotes/groovy-4.0.adoc b/site/src/site/releasenotes/groovy-4.0.adoc
index f7a3d41..6c1f3fa 100644
--- a/site/src/site/releasenotes/groovy-4.0.adoc
+++ b/site/src/site/releasenotes/groovy-4.0.adoc
@@ -93,6 +93,163 @@ to help improve overall performance of the indy bytecode.
 [[Groovy4.0-new]]
 == New features
 
+[[Groovy4.0-switch-expressions]]
+=== Switch expressions
+
+Groovy has always had a very powerful switch _statement_, but there are times when
+a switch _expression_ would be more convenient.
+
+In switch statements, case branches with fallthrough behavior are usually much rarer
+than branches which handle one case and then break out of the switch.
+The `break` statements clutter the code as shown here.
+
+[source,groovy]
+--------------------------------------
+def result
+switch(i) {
+  case 0: result = 'zero'; break
+  case 1: result = 'one'; break
+  case 2: result = 'two'; break
+  default: throw new IllegalStateException('unknown number')
+}
+--------------------------------------
+
+A common trick is to introduce a method to wrap the switch.
+In simple cases, multiple statements might reduce to a single return statement.
+The `break` statements are gone, albeit replaced by `return` statements.
+
+[source,groovy]
+--------------------------------------
+def stringify(int i) {
+  switch(i) {
+    case 0: return 'zero'
+    case 1: return 'one'
+    case 2: return 'two'
+    default: throw new IllegalStateException('unknown number')
+  }
+}
+
+def result = stringify(i)
+--------------------------------------
+
+Switch expressions (borrowing heavily from Java) provide a nicer alternative still:
+
+[source,groovy]
+--------------------------------------
+def result = switch(i) {
+    case 0 -> 'zero'
+    case 1 -> 'one'
+    case 2 -> 'two'
+    default -> throw new IllegalStateException('unknown number')
+}
+--------------------------------------
+
+Here, the right-hand side (following the `\->`) must be a single expression. If multiple statements are needed, a block can be used.
+For example, the first case branch from the previous example could be re-written as:
+
+[source,groovy]
+--------------------------------------
+    case 0 -> { def a = 'ze'; def b = 'ro'; a + b }
+--------------------------------------
+
+Switch expressions can also use the traditional `:` form with multiple statements
+but in this case, a `yield` statement must be executed.
+
+[source,groovy]
+--------------------------------------
+def result = switch(i) {
+    case 0:
+        def a = 'ze'
+        def b = 'ro'
+        if (true) yield a + b
+        else yield b + a
+    case 1:
+        yield 'one'
+    case 2:
+        yield 'two'
+    default:
+        throw new IllegalStateException('unknown number')
+}
+--------------------------------------
+
+The `\->` and `:` forms cannot be mixed.
+
+All of the normal Groovy case expressions are still catered for, e.g.:
+
+[source,groovy]
+--------------------------------------
+class Custom {
+  def isCase(o) { o == -1 }
+}
+
+class Coord {
+  int x, y
+}
+
+def items = [10, -1, 5, null, 41, 3.5f, 38, 99, new Coord(x: 4, y: 5), 'foo']
+def result = items.collect { a ->
+  switch(a) {
+    case null -> 'null'
+    case 5 -> 'five'
+    case new Custom() -> 'custom'
+    case 0..15 -> 'range'
+    case [37, 41, 43] -> 'prime'
+    case Float -> 'float'
+    case { it instanceof Number && it % 2 == 0 } -> 'even'
+    case Coord -> a.with { "x: $x, y: $y" }
+    case ~/../ -> 'two chars'
+    default -> 'none of the above'
+  }
+}
+
+assert result == ['range', 'custom', 'five', 'null', 'prime', 'float',
+                  'even', 'two chars', 'x: 4, y: 5', 'none of the above']
+--------------------------------------
+
+Switch expressions are particularly handy for cases where
+the visitor pattern might have been traditionally used, e.g.:
+
+[source,groovy]
+--------------------------------------
+import groovy.transform.Immutable
+
+interface Expr { }
+@Immutable class IntExpr implements Expr { int i }
+@Immutable class NegExpr implements Expr { Expr n }
+@Immutable class AddExpr implements Expr { Expr left, right }
+@Immutable class MulExpr implements Expr { Expr left, right }
+
+int eval(Expr e) {
+    e.with {
+        switch(it) {
+            case IntExpr -> i
+            case NegExpr -> -eval(n)
+            case AddExpr -> eval(left) + eval(right)
+            case MulExpr -> eval(left) * eval(right)
+            default -> throw new IllegalStateException()
+        }
+    }
+}
+
+@Newify(pattern=".*Expr")
+def test() {
+    def exprs = [
+        IntExpr(4),
+        NegExpr(IntExpr(4)),
+        AddExpr(IntExpr(4), MulExpr(IntExpr(3), IntExpr(2))), // 4 + (3*2)
+        MulExpr(IntExpr(4), AddExpr(IntExpr(3), IntExpr(2)))  // 4 * (3+2)
+    ]
+    assert exprs.collect { eval(it) } == [4, -4, 10, 20]
+}
+
+test()
+--------------------------------------
+
+==== Differences to Java
+
+* Currently, there is no requirement that all possible values of the switch target
+are covered exhaustively by case branches.
+
 [[Groovy4.0-new-checkers]]
 === Built-in type checkers