diff --git a/_overviews/scala3-book/control-structures.md b/_overviews/scala3-book/control-structures.md index 8b0500243e..736501a30b 100644 --- a/_overviews/scala3-book/control-structures.md +++ b/_overviews/scala3-book/control-structures.md @@ -28,20 +28,53 @@ These are all demonstrated in the following sections. A one-line Scala `if` statement looks like this: +{% tabs control-structures-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-1 %} +```scala +if (x == 1) println(x) +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-1 %} ```scala if x == 1 then println(x) ``` +{% endtab %} +{% endtabs %} When you need to run multiple lines of code after an `if` equality comparison, use this syntax: +{% tabs control-structures-2 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-2 %} +```scala +if (x == 1) { + println("x is 1, as you can see:") + println(x) +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-2 %} ```scala if x == 1 then println("x is 1, as you can see:") println(x) ``` +{% endtab %} +{% endtabs %} The `if`/`else` syntax looks like this: +{% tabs control-structures-3 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-3 %} +```scala +if (x == 1) { + println("x is 1, as you can see:") + println(x) +} else { + println("x was not 1") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-3 %} ```scala if x == 1 then println("x is 1, as you can see:") @@ -49,9 +82,23 @@ if x == 1 then else println("x was not 1") ``` +{% endtab %} +{% endtabs %} And this is the `if`/`else if`/`else` syntax: +{% tabs control-structures-4 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-4 %} +```scala +if (x < 0) + println("negative") +else if (x == 0) + println("zero") +else + println("positive") +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-4 %} ```scala if x < 0 then println("negative") @@ -60,9 +107,16 @@ else if x == 0 then else println("positive") ``` +{% endtab %} +{% endtabs %} -You can optionally include an `end if` statement at the end of each expression, if you prefer: +### `end if` statement +
+  This is new in Scala 3, and not supported in Scala 2. +
+ +You can optionally include an `end if` statement at the end of each expression, if you prefer: ```scala if x == 1 then println("x is 1, as you can see:") @@ -70,18 +124,40 @@ if x == 1 then end if ``` - ### `if`/`else` expressions always return a result Note that `if`/`else` comparisons form _expressions_, meaning that they return a value which you can assign to a variable. Because of this, there’s no need for a special ternary operator: +{% tabs control-structures-6 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-6 %} +```scala +val minValue = if (a < b) a else b +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-6 %} ```scala val minValue = if a < b then a else b ``` +{% endtab %} +{% endtabs %} + Because they return a value, you can use `if`/`else` expressions as the body of a method: +{% tabs control-structures-7 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-7 %} +```scala +def compare(a: Int, b: Int): Int = + if (a < b) + -1 + else if (a == b) + 0 + else + 1 +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-7 %} ```scala def compare(a: Int, b: Int): Int = if a < b then @@ -91,49 +167,87 @@ def compare(a: Int, b: Int): Int = else 1 ``` +{% endtab %} +{% endtabs %} ### Aside: Expression-oriented programming As a brief note about programming in general, when every expression you write returns a value, that style is referred to as _expression-oriented programming_, or EOP. For example, this is an _expression_: +{% tabs control-structures-8 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-8 %} +```scala +val minValue = if (a < b) a else b +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-8 %} ```scala val minValue = if a < b then a else b ``` +{% endtab %} +{% endtabs %} Conversely, lines of code that don’t return values are called _statements_, and they’re used for their _side-effects_. For example, these lines of code don’t return values, so they’re used for their side effects: +{% tabs control-structures-9 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-9 %} +```scala +if (a == b) action() +println("Hello") +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-9 %} ```scala if a == b then action() println("Hello") ``` +{% endtab %} +{% endtabs %} The first example runs the `action` method as a side effect when `a` is equal to `b`. The second example is used for the side effect of printing a string to STDOUT. As you learn more about Scala you’ll find yourself writing more _expressions_ and fewer _statements_. - - ## `for` loops In its most simple use, a Scala `for` loop can be used to iterate over the elements in a collection. For example, given a sequence of integers, you can loop over its elements and print their values like this: +{% tabs control-structures-10 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-10 %} +```scala +val ints = Seq(1, 2, 3) +for (i <- ints) println(i) +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-10 %} ```scala val ints = Seq(1, 2, 3) for i <- ints do println(i) ``` +{% endtab %} +{% endtabs %} -The code `i <- ints` is referred to as a _generator_, and if you leave the parentheses off of the generator, the `do` keyword is required before the code that follows it. -Otherwise you can write the code like this: -```scala -for (i <- ints) println(i) -``` +The code `i <- ints` is referred to as a _generator_. -Regardless of which approach you use, this is what the result looks like in the Scala REPL: +This is what the result looks like in the Scala REPL: +{% tabs control-structures-11 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-11 %} +```` +scala> val ints = Seq(1,2,3) +ints: Seq[Int] = List(1, 2, 3) + +scala> for (i <- ints) println(i) +1 +2 +3 +```` +{% endtab %} +{% tab 'Scala 3' for=control-structures-11 %} ```` scala> val ints = Seq(1,2,3) ints: Seq[Int] = List(1, 2, 3) @@ -143,21 +257,49 @@ scala> for i <- ints do println(i) 2 3 ```` +{% endtab %} +{% endtabs %} + When you need a multiline block of code following the `for` generator, use the following syntax: +{% tabs control-structures-12 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-12 %} ```scala -for - i <- ints +for (i <- ints) { + val x = i * 2 + println(s"i = $i, x = $x") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-12 %} +```scala +for i <- ints do val x = i * 2 println(s"i = $i, x = $x") ``` +{% endtab %} +{% endtabs %} + ### Multiple generators `for` loops can have multiple generators, as shown in this example: +{% tabs control-structures-13 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-13 %} +```scala +for { + i <- 1 to 2 + j <- 'a' to 'b' + k <- 1 to 10 by 5 +} { + println(s"i = $i, j = $j, k = $k") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-13 %} ```scala for i <- 1 to 2 @@ -166,6 +308,9 @@ for do println(s"i = $i, j = $j, k = $k") ``` +{% endtab %} +{% endtabs %} + That expression prints this output: @@ -184,6 +329,18 @@ i = 2, j = b, k = 6 `for` loops can also contain `if` statements, which are known as _guards_: +{% tabs control-structures-14 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-14 %} +```scala +for { + i <- 1 to 5 + if i % 2 == 0 +} { + println(i) +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-14 %} ```scala for i <- 1 to 5 @@ -191,6 +348,9 @@ for do println(i) ``` +{% endtab %} +{% endtabs %} + The output of that loop is: @@ -202,6 +362,20 @@ The output of that loop is: A `for` loop can have as many guards as needed. This example shows one way to print the number `4`: +{% tabs control-structures-15 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-15 %} +```scala +for { + i <- 1 to 10 + if i > 3 + if i < 6 + if i % 2 == 0 +} { + println(i) +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-15 %} ```scala for i <- 1 to 10 @@ -211,6 +385,8 @@ for do println(i) ``` +{% endtab %} +{% endtabs %} ### Using `for` with Maps @@ -227,18 +403,39 @@ val states = Map( You can print the keys and values using `for`, like this: +{% tabs control-structures-16 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-16 %} +```scala +for ((abbrev, fullName) <- states) println(s"$abbrev: $fullName") +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-16 %} ```scala for (abbrev, fullName) <- states do println(s"$abbrev: $fullName") ``` +{% endtab %} +{% endtabs %} Here’s what that looks like in the REPL: +{% tabs control-structures-17 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-17 %} +```scala +scala> for ((abbrev, fullName) <- states) println(s"$abbrev: $fullName") +AK: Alaska +AL: Alabama +AR: Arizona +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-17 %} ```scala scala> for (abbrev, fullName) <- states do println(s"$abbrev: $fullName") AK: Alaska AL: Alabama AR: Arizona ``` +{% endtab %} +{% endtabs %} As the `for` loop iterates over the map, each key/value pair is bound to the variables `abbrev` and `fullName`, which are in a tuple: @@ -248,8 +445,6 @@ As the `for` loop iterates over the map, each key/value pair is bound to the var As the loop runs, the variable `abbrev` is assigned to the current _key_ in the map, and the variable `fullName` is assigned to the current map _value_. - - ## `for` expressions In the previous `for` loop examples, those loops were all used for _side effects_, specifically to print those values to STDOUT using `println`. @@ -257,15 +452,27 @@ In the previous `for` loop examples, those loops were all used for _side effects It’s important to know that you can also create `for` _expressions_ that return values. You create a `for` expression by adding the `yield` keyword and an expression to return, like this: +{% tabs control-structures-18 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-18 %} ```scala val list = - for - i <- 10 to 12 - yield - i * 2 + for (i <- 10 to 12) + yield i * 2 + +// list: IndexedSeq[Int] = Vector(20, 22, 24) +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-18 %} +```scala +val list = + for i <- 10 to 12 + yield i * 2 // list: IndexedSeq[Int] = Vector(20, 22, 24) ``` +{% endtab %} +{% endtabs %} + After that `for` expression runs, the variable `list` is a `Vector` that contains the values shown. This is how the expression works: @@ -292,23 +499,53 @@ val list = (10 to 12).map(i => i * 2) Here’s an example that shows how to use a block of code after the `yield`: +{% tabs control-structures-19 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-19 %} ```scala val names = List("_olivia", "_walter", "_peter") -val capNames = for name <- names yield +val capNames = for (name <- names) yield { val nameWithoutUnderscore = name.drop(1) val capName = nameWithoutUnderscore.capitalize capName +} // capNames: List[String] = List(Olivia, Walter, Peter) ``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-19 %} +```scala +val names = List("_olivia", "_walter", "_peter") +val capNames = for name <- names yield + val nameWithoutUnderscore = name.drop(1) + val capName = nameWithoutUnderscore.capitalize + capName + +// capNames: List[String] = List(Olivia, Walter, Peter) +``` +{% endtab %} +{% endtabs %} ### Using a `for` expression as the body of a method Because a `for` expression yields a result, it can be used as the body of a method that returns a useful value. This method returns all of the values in a given list of integers that are between `3` and `10`: +{% tabs control-structures-20 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-20 %} +```scala +def between3and10(xs: List[Int]): List[Int] = + for { + x <- xs + if x >= 3 + if x <= 10 + } yield x + +between3and10(List(1, 3, 7, 11)) // : List[Int] = List(3, 7) +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-20 %} ```scala def between3and10(xs: List[Int]): List[Int] = for @@ -319,33 +556,34 @@ def between3and10(xs: List[Int]): List[Int] = between3and10(List(1, 3, 7, 11)) // : List[Int] = List(3, 7) ``` - - +{% endtab %} +{% endtabs %} ## `while` loops Scala `while` loop syntax looks like this: +{% tabs control-structures-21 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-21 %} ```scala var i = 0 -while i < 3 do +while (i < 3) { println(i) i += 1 +} ``` - -If you use parentheses around the test condition, it can also be written like this: - +{% endtab %} +{% tab 'Scala 3' for=control-structures-21 %} ```scala var i = 0 -while (i < 3) { +while i < 3 do println(i) i += 1 -} ``` - - +{% endtab %} +{% endtabs %} ## `match` expressions @@ -354,9 +592,24 @@ Pattern matching is a major feature of functional programming languages, and Sca In the most simple case you can use a `match` expression like a Java `switch` statement, matching cases based on an integer value. Notice that this really is an expression, as it evaluates to a result: +{% tabs control-structures-22 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-22 %} +```scala +// `i` is an integer +val day = i match { + case 0 => "Sunday" + case 1 => "Monday" + case 2 => "Tuesday" + case 3 => "Wednesday" + case 4 => "Thursday" + case 5 => "Friday" + case 6 => "Saturday" + case _ => "invalid day" // the default, catch-all +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-22 %} ```scala -import scala.annotation.switch - // `i` is an integer val day = i match case 0 => "Sunday" @@ -368,6 +621,9 @@ val day = i match case 6 => "Saturday" case _ => "invalid day" // the default, catch-all ``` +{% endtab %} +{% endtabs %} + In this example, the variable `i` is tested against the cases shown. If it’s between `0` and `6`, `day` is bound to the string that represents that day of the week. @@ -383,15 +639,42 @@ Since the cases are considered in the order they are written, and the first matc When you need to access the catch-all, default value in a `match` expression, just provide a variable name on the left side of the `case` statement instead of `_`, and then use that variable name on the right side of the statement as needed: +{% tabs control-structures-23 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-23 %} +```scala +i match { + case 0 => println("1") + case 1 => println("2") + case what => println(s"You gave me: $what") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-23 %} ```scala i match case 0 => println("1") case 1 => println("2") case what => println(s"You gave me: $what") ``` +{% endtab %} +{% endtabs %} + The name used in the pattern must begin with a lowercase letter. A name beginning with an uppercase letter does not introduce a variable, but matches a value in scope: +{% tabs control-structures-24 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-24 %} +```scala +val N = 42 +i match { + case 0 => println("1") + case 1 => println("2") + case N => println("42") + case n => println(s"You gave me: $n" ) +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-24 %} ```scala val N = 42 i match @@ -400,6 +683,9 @@ i match case N => println("42") case n => println(s"You gave me: $n" ) ``` +{% endtab %} +{% endtabs %} + If `i` is equal to `42`, then `case N` will match, and it will print the string `"42"`. It won't reach the default case. ### Handling multiple possible matches on one line @@ -407,19 +693,43 @@ If `i` is equal to `42`, then `case N` will match, and it will print the string As mentioned, `match` expressions have many capabilities. This example shows how to use multiple possible pattern matches in each `case` statement: +{% tabs control-structures-25 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-25 %} +```scala +val evenOrOdd = i match { + case 1 | 3 | 5 | 7 | 9 => println("odd") + case 2 | 4 | 6 | 8 | 10 => println("even") + case _ => println("some other number") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-25 %} ```scala val evenOrOdd = i match case 1 | 3 | 5 | 7 | 9 => println("odd") case 2 | 4 | 6 | 8 | 10 => println("even") case _ => println("some other number") ``` - +{% endtab %} +{% endtabs %} ### Using `if` guards in `case` clauses You can also use guards in the `case`s of a match expression. In this example the second and third `case` both use guards to match multiple integer values: +{% tabs control-structures-26 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-26 %} +```scala +i match { + case 1 => println("one, a lonely number") + case x if x == 2 || x == 3 => println("two’s company, three’s a crowd") + case x if x > 3 => println("4+, that’s a party") + case _ => println("i’m guessing your number is zero or less") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-26 %} ```scala i match case 1 => println("one, a lonely number") @@ -427,9 +737,24 @@ i match case x if x > 3 => println("4+, that’s a party") case _ => println("i’m guessing your number is zero or less") ``` +{% endtab %} +{% endtabs %} + Here’s another example, which shows how to match a given value against ranges of numbers: +{% tabs control-structures-27 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-27 %} +```scala +i match { + case a if 0 to 9 contains a => println(s"0-9 range: $a") + case b if 10 to 19 contains b => println(s"10-19 range: $b") + case c if 20 to 29 contains c => println(s"20-29 range: $c") + case _ => println("Hmmm...") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-27 %} ```scala i match case a if 0 to 9 contains a => println(s"0-9 range: $a") @@ -437,6 +762,9 @@ i match case c if 20 to 29 contains c => println(s"20-29 range: $c") case _ => println("Hmmm...") ``` +{% endtab %} +{% endtabs %} + #### Case classes and match expressions @@ -444,29 +772,58 @@ i match You can also extract fields from `case` classes---and classes that have properly written `apply`/`unapply` methods---and use those in your guard conditions. Here’s an example using a simple `Person` case class: +{% tabs control-structures-28 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-28 %} ```scala case class Person(name: String) -def speak(p: Person) = p match +def speak(p: Person) = p match { case Person(name) if name == "Fred" => println(s"$name says, Yubba dubba doo") case Person(name) if name == "Bam Bam" => println(s"$name says, Bam bam!") case _ => println("Watch the Flintstones!") +} speak(Person("Fred")) // "Fred says, Yubba dubba doo" speak(Person("Bam Bam")) // "Bam Bam says, Bam bam!" ``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-28 %} +```scala +case class Person(name: String) + +def speak(p: Person) = p match + case Person(name) if name == "Fred" => println(s"$name says, Yubba dubba doo") + case Person(name) if name == "Bam Bam" => println(s"$name says, Bam bam!") + case _ => println("Watch the Flintstones!") +speak(Person("Fred")) // "Fred says, Yubba dubba doo" +speak(Person("Bam Bam")) // "Bam Bam says, Bam bam!" +``` +{% endtab %} +{% endtabs %} ### Using a `match` expression as the body of a method Because `match` expressions return a value, they can be used as the body of a method. This method takes a `Matchable` value as an input parameter, and returns a `Boolean`, based on the result of the `match` expression: +{% tabs control-structures-29 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-29 %} +```scala +def isTruthy(a: Matchable) = a match { + case 0 | "" | false => false + case _ => true +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-29 %} ```scala def isTruthy(a: Matchable) = a match case 0 | "" | false => false case _ => true ``` +{% endtab %} +{% endtabs %} The input parameter `a` is defined to be the [`Matchable` type][matchable]---which is the root of all Scala types that pattern matching can be performed on. The method is implemented by matching on the input, providing two cases: @@ -485,7 +842,6 @@ isTruthy(2F) // true Using a `match` expression as the body of a method is a very common use. - #### Match expressions support many different types of patterns There are many different forms of patterns that can be used to write `match` expressions. Examples includes: @@ -498,6 +854,46 @@ Examples includes: All of these kinds of patterns are shown in the following `pattern` method, which takes an input parameter of type `Matchable` and returns a `String`: +{% tabs control-structures-30 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-30 %} +```scala +def pattern(x: Matchable): String = x match { + + // constant patterns + case 0 => "zero" + case true => "true" + case "hello" => "you said 'hello'" + case Nil => "an empty List" + + // sequence patterns + case List(0, _, _) => "a 3-element list with 0 as the first element" + case List(1, _*) => "list, starts with 1, has any number of elements" + case Vector(1, _*) => "vector, starts w/ 1, has any number of elements" + + // tuple patterns + case (a, b) => s"got $a and $b" + case (a, b, c) => s"got $a, $b, and $c" + + // constructor patterns + case Person(first, "Alexander") => s"Alexander, first name = $first" + case Dog("Zeus") => "found a dog named Zeus" + + // type test patterns + case s: String => s"got a string: $s" + case i: Int => s"got an int: $i" + case f: Float => s"got a float: $f" + case a: Array[Int] => s"array of int: ${a.mkString(",")}" + case as: Array[String] => s"string array: ${as.mkString(",")}" + case d: Dog => s"dog: ${d.name}" + case list: List[?] => s"got a List: $list" + case m: Map[?, ?] => m.toString + + // the default wildcard pattern + case _ => "Unknown" +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-30 %} ```scala def pattern(x: Matchable): String = x match @@ -533,14 +929,14 @@ def pattern(x: Matchable): String = x match // the default wildcard pattern case _ => "Unknown" ``` +{% endtab %} +{% endtabs %} {% comment %} TODO: Add in the new Scala 3 syntax shown on this page: http://dotty.epfl.ch/docs/reference/changed-features/match-syntax.html {% endcomment %} - - ## try/catch/finally Like Java, Scala has a `try`/`catch`/`finally` construct to let you catch and manage exceptions. @@ -548,6 +944,22 @@ For consistency, Scala uses the same syntax that `match` expressions use and sup In the following example, `openAndReadAFile` is a method that does what its name implies: it opens a file and reads the text in it, assigning the result to the mutable variable `text`: +{% tabs control-structures-31 class=tabs-scala-version %} +{% tab 'Scala 2' for=control-structures-31 %} +```scala +var text = "" +try { + text = openAndReadAFile(filename) +} catch { + case fnf: FileNotFoundException => fnf.printStackTrace() + case ioe: IOException => ioe.printStackTrace() +} finally { + // close your resources here + println("Came to the 'finally' clause.") +} +``` +{% endtab %} +{% tab 'Scala 3' for=control-structures-31 %} ```scala var text = "" try @@ -559,8 +971,9 @@ finally // close your resources here println("Came to the 'finally' clause.") ``` +{% endtab %} +{% endtabs %} Assuming that the `openAndReadAFile` method uses the Java `java.io.*` classes to read a file and doesn’t catch its exceptions, attempting to open and read a file can result in both a `FileNotFoundException` and an `IOException`, and those two exceptions are caught in the `catch` block of this example. - [matchable]: {{ site.scala3ref }}/other-new-features/matchable.html diff --git a/_tour/pattern-matching.md b/_tour/pattern-matching.md index 72afeb1902..0dfbb01b4d 100644 --- a/_tour/pattern-matching.md +++ b/_tour/pattern-matching.md @@ -15,6 +15,8 @@ Pattern matching is a mechanism for checking a value against a pattern. A succes ## Syntax A match expression has a value, the `match` keyword, and at least one `case` clause. +{% tabs pattern-matching-1 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-1 %} ```scala mdoc import scala.util.Random @@ -27,9 +29,26 @@ x match { case _ => "other" } ``` +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-1 %} +```scala +import scala.util.Random + +val x: Int = Random.nextInt(10) + +x match + case 0 => "zero" + case 1 => "one" + case 2 => "two" + case _ => "other" +``` +{% endtab %} +{% endtabs %} The `val x` above is a random integer between 0 and 10. `x` becomes the left operand of the `match` operator and on the right is an expression with four cases. The last case `_` is a "catch all" case for any other possible `Int` values. Cases are also called _alternatives_. Match expressions have a value. +{% tabs pattern-matching-2 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-2 %} ```scala mdoc def matchTest(x: Int): String = x match { case 1 => "one" @@ -39,6 +58,20 @@ def matchTest(x: Int): String = x match { matchTest(3) // returns other matchTest(1) // returns one ``` +{% endtab %} + +{% tab 'Scala 3' for=pattern-matching-2 %} +```scala +def matchTest(x: Int): String = x match + case 1 => "one" + case 2 => "two" + case _ => "other" + +matchTest(3) // returns other +matchTest(1) // returns one +``` +{% endtab %} +{% endtabs %} This match expression has a type String because all of the cases return String. Therefore, the function `matchTest` returns a String. ## Matching on case classes @@ -46,19 +79,19 @@ This match expression has a type String because all of the cases return String. Case classes are especially useful for pattern matching. ```scala mdoc -abstract class Notification +sealed trait Notification case class Email(sender: String, title: String, body: String) extends Notification case class SMS(caller: String, message: String) extends Notification case class VoiceRecording(contactName: String, link: String) extends Notification - - ``` -`Notification` is an abstract super class which has three concrete Notification types implemented with case classes `Email`, `SMS`, and `VoiceRecording`. Now we can do pattern matching on these case classes: +`Notification` is a sealed trait which has three concrete Notification types implemented with case classes `Email`, `SMS`, and `VoiceRecording`. Now we can do pattern matching on these case classes: -``` +{% tabs pattern-matching-4 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-4 %} +```scala def showNotification(notification: Notification): String = { notification match { case Email(sender, title, _) => @@ -76,12 +109,36 @@ println(showNotification(someSms)) // prints You got an SMS from 12345! Message println(showNotification(someVoiceRecording)) // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123 ``` +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-4 %} +```scala +def showNotification(notification: Notification): String = + notification match + case Email(sender, title, _) => + s"You got an email from $sender 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" + +val someSms = SMS("12345", "Are you there?") +val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") + +println(showNotification(someSms)) // prints You got an SMS from 12345! Message: Are you there? + +println(showNotification(someVoiceRecording)) // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123 +``` +{% endtab %} +{% endtabs %} + The function `showNotification` takes as a parameter the abstract type `Notification` and matches on the type of `Notification` (i.e. it figures out whether it's an `Email`, `SMS`, or `VoiceRecording`). In the `case Email(sender, title, _)` the fields `sender` and `title` are used in the return value but the `body` field is ignored with `_`. ## Pattern guards Pattern guards are simply boolean expressions which are used to make cases more specific. Just add `if ` after the pattern. -``` +{% tabs pattern-matching-5 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-5 %} +```scala def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = { notification match { case Email(sender, _, _) if importantPeopleInfo.contains(sender) => @@ -106,13 +163,42 @@ println(showImportantNotification(importantEmail, importantPeopleInfo)) // print println(showImportantNotification(importantSms, importantPeopleInfo)) // prints You got an SMS from special someone! ``` +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-5 %} +```scala +def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = + notification match + case Email(sender, _, _) if importantPeopleInfo.contains(sender) => + "You got an email from special someone!" + case SMS(number, _) if importantPeopleInfo.contains(number) => + "You got an SMS from special someone!" + case other => + showNotification(other) // nothing special, delegate to our original showNotification function + +val importantPeopleInfo = Seq("867-5309", "jenny@gmail.com") + +val someSms = SMS("123-4567", "Are you there?") +val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") +val importantEmail = Email("jenny@gmail.com", "Drinks tonight?", "I'm free after 5!") +val importantSms = SMS("867-5309", "I'm here! Where are you?") + +println(showImportantNotification(someSms, importantPeopleInfo)) // prints You got an SMS from 123-4567! Message: Are you there? +println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123 +println(showImportantNotification(importantEmail, importantPeopleInfo)) // prints You got an email from special someone! + +println(showImportantNotification(importantSms, importantPeopleInfo)) // prints You got an SMS from special someone! +``` +{% endtab %} +{% endtabs %} In the `case Email(sender, _, _) if importantPeopleInfo.contains(sender)`, the pattern is matched only if the `sender` is in the list of important people. ## Matching on type only You can match on the type like so: +{% tabs pattern-matching-6 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-6 %} ```scala mdoc -abstract class Device +sealed trait Device case class Phone(model: String) extends Device { def screenOff = "Turning screen off" } @@ -120,26 +206,60 @@ case class Computer(model: String) extends Device { def screenSaverOn = "Turning screen saver on..." } -def goIdle(device: Device) = device match { +def goIdle(device: Device): String = device match { case p: Phone => p.screenOff case c: Computer => c.screenSaverOn } ``` +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-6 %} +```scala +sealed trait Device +case class Phone(model: String) extends Device: + def screenOff = "Turning screen off" + +case class Computer(model: String) extends Device: + def screenSaverOn = "Turning screen saver on..." + + +def goIdle(device: Device): String = device match + case p: Phone => p.screenOff + case c: Computer => c.screenSaverOn +``` +{% endtab %} +{% endtabs %} + `def goIdle` has a different behavior depending on the type of `Device`. This is useful when the case needs to call a method on the pattern. It is a convention to use the first letter of the type as the case identifier (`p` and `c` in this case). ## Sealed classes Traits and classes can be marked `sealed` which means all subtypes must be declared in the same file. This assures that all subtypes are known. +{% tabs pattern-matching-7 class=tabs-scala-version %} +{% tab 'Scala 2' for=pattern-matching-7 %} ```scala mdoc -sealed abstract class Furniture -case class Couch() extends Furniture -case class Chair() extends Furniture +sealed trait Furniture +case class Couch(descriptor: String) extends Furniture +case class Chair(descriptor: String) extends Furniture def findPlaceToSit(piece: Furniture): String = piece match { - case a: Couch => "Lie on the couch" - case b: Chair => "Sit on the chair" + case Couch(descriptor) => s"Lie on the $descriptor couch" + case Chair(descriptor) => s"Sit on the $descriptor chair" } ``` +{% endtab %} +{% tab 'Scala 3' for=pattern-matching-7 %} +```scala +sealed trait Furniture +case class Couch(descriptor: String) extends Furniture +case class Chair(descriptor: String) extends Furniture + +def findPlaceToSit(piece: Furniture): String = piece match + case Couch(descriptor) => s"Lie on the $descriptor couch" + case Chair(descriptor) => s"Sit on the $descriptor chair" +``` +{% endtab %} +{% endtabs %} + This is useful for pattern matching because we don't need a "catch all" case. ## Notes