diff --git a/_getting-started/intellij-track/getting-started-with-scala-in-intellij.md b/_getting-started/intellij-track/getting-started-with-scala-in-intellij.md index 73339d4360..9b99acabd3 100644 --- a/_getting-started/intellij-track/getting-started-with-scala-in-intellij.md +++ b/_getting-started/intellij-track/getting-started-with-scala-in-intellij.md @@ -98,11 +98,15 @@ A good way to try out code samples is with Scala Worksheets 2. Name your new Scala worksheet "Mathematician". 3. Enter the following code into the worksheet: +{% tabs square %} +{% tab 'Scala 2 and 3' for=square %} ``` def square(x: Int): Int = x * x square(2) ``` +{% endtab %} +{% endtabs %} As you change your code, you'll notice that it gets evaluated in the right pane. If you do not see a right pane, right-click on your Scala worksheet in the Project pane, and click on Evaluate Worksheet. diff --git a/_overviews/collections-2.13/maps.md b/_overviews/collections-2.13/maps.md index 9fe1af00d7..3b512ce5db 100644 --- a/_overviews/collections-2.13/maps.md +++ b/_overviews/collections-2.13/maps.md @@ -108,11 +108,17 @@ def f(x: String): String Assume further that `f` has no side-effects, so invoking it again with the same argument will always yield the same result. In that case you could save time by storing previously computed bindings of argument and results of `f` in a map and only computing the result of `f` if a result of an argument was not found there. One could say the map is a _cache_ for the computations of the function `f`. +{% tabs cache-creation %} +{% tab 'Scala 2 and 3' for=cache-creation %} scala> val cache = collection.mutable.Map[String, String]() cache: scala.collection.mutable.Map[String,String] = Map() +{% endtab %} +{% endtabs %} You can now create a more efficient caching version of the `f` function: +{% tabs cache-usage %} +{% tab 'Scala 2 and 3' for=cache-usage %} scala> def cachedF(s: String): String = cache.getOrElseUpdate(s, f(s)) cachedF: (s: String)String scala> cachedF("abc") @@ -120,6 +126,8 @@ You can now create a more efficient caching version of the `f` function: res3: String = cba scala> cachedF("abc") res4: String = cba +{% endtab %} +{% endtabs %} Note that the second argument to `getOrElseUpdate` is "by-name", so the computation of `f("abc")` above is only performed if `getOrElseUpdate` requires the value of its second argument, which is precisely if its first argument is not found in the `cache` map. You could also have implemented `cachedF` directly, using just basic map operations, but it would take more code to do so: diff --git a/_overviews/core/futures.md b/_overviews/core/futures.md index c44c96bae7..eec0e4529c 100644 --- a/_overviews/core/futures.md +++ b/_overviews/core/futures.md @@ -40,9 +40,13 @@ environment to resize itself if necessary to guarantee progress. A typical future looks like this: +{% tabs futures-00 %} +{% tab 'Scala 2 and 3' for=futures-00 %} val inverseFuture: Future[Matrix] = Future { fatMatrix.inverse() // non-blocking long lasting computation }(executionContext) +{% endtab %} +{% endtabs %} Or with the more idiomatic: @@ -165,6 +169,8 @@ As explained in the `ForkJoinPool` API, this is only possible if the pool is exp Fortunately the concurrent package provides a convenient way for doing so: +{% tabs blocking %} +{% tab 'Scala 2 and 3' for=blocking %} import scala.concurrent.Future import scala.concurrent.blocking @@ -174,6 +180,8 @@ Fortunately the concurrent package provides a convenient way for doing so: // ... } } +{% endtab %} +{% endtabs %} Note that `blocking` is a general construct that will be discussed more in depth [below](#blocking-inside-a-future). @@ -216,26 +224,43 @@ If you need to wrap long lasting blocking operations we recommend using a dedica Using the `ExecutionContext.fromExecutor` method you can wrap a Java `Executor` into an `ExecutionContext`. For instance: +{% tabs executor class=tabs-scala-version %} + +{% tab 'Scala 2' for=executor %} ExecutionContext.fromExecutor(new ThreadPoolExecutor( /* your configuration */ )) +{% endtab %} +{% tab 'Scala 3' for=executor %} + ExecutionContext.fromExecutor(ThreadPoolExecutor( /* your configuration */ )) +{% endtab %} + +{% endtabs %} ### Synchronous Execution Context One might be tempted to have an `ExecutionContext` that runs computations within the current thread: +{% tabs bad-example %} +{% tab 'Scala 2 and 3' for=bad-example %} val currentThreadExecutionContext = ExecutionContext.fromExecutor( new Executor { // Do not do this! def execute(runnable: Runnable) = runnable.run() }) +{% endtab %} +{% endtabs %} This should be avoided as it introduces non-determinism in the execution of your future. +{% tabs bad-example-2 %} +{% tab 'Scala 2 and 3' for=bad-example-2 %} Future { doSomething }(ExecutionContext.global).map { doSomethingElse }(currentThreadExecutionContext) +{% endtab %} +{% endtabs %} The `doSomethingElse` call might either execute in `doSomething`'s thread or in the main thread, and therefore be either asynchronous or synchronous. As explained [here](https://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/) a callback should not be both. @@ -323,10 +348,14 @@ the following example, the `session` value is incorrectly initialized, so the computation in the `Future` block will throw a `NullPointerException`. This future `f` is then failed with this exception instead of being completed successfully: +{% tabs futures-04b %} +{% tab 'Scala 2 and 3' for=futures-04b %} val session = null val f: Future[List[Friend]] = Future { session.getFriends() } +{% endtab %} +{% endtabs %} The line `import ExecutionContext.Implicits.global` above imports the default global execution context. @@ -348,10 +377,14 @@ This computation may involve blocking while the file contents are being retrieved from the disk, so it makes sense to perform it concurrently with the rest of the computation. +{% tabs futures-04c %} +{% tab 'Scala 2 and 3' for=futures-04c %} val firstOccurrence: Future[Int] = Future { val source = scala.io.Source.fromFile("myText.txt") source.toSeq.indexOfSlice("myKeyword") } +{% endtab %} +{% endtabs %} ### Callbacks @@ -461,6 +494,8 @@ Coming back to the previous example with searching for the first occurrence of a keyword, you might want to print the position of the keyword to the screen: +{% tabs futures-oncomplete %} +{% tab 'Scala 2 and 3' for=futures-oncomplete %} val firstOccurrence: Future[Int] = Future { val source = scala.io.Source.fromFile("myText.txt") source.toSeq.indexOfSlice("myKeyword") @@ -470,6 +505,8 @@ of the keyword to the screen: case Success(idx) => println("The keyword first appears at position: " + idx) case Failure(t) => println("Could not process file: " + t.getMessage) } +{% endtab %} +{% endtabs %} The `onComplete` and `foreach` methods both have result type `Unit`, which @@ -496,6 +533,8 @@ This means that in the following example the variable `totalA` may not be set to the correct number of lower case and upper case `a` characters from the computed text. +{% tabs volatile %} +{% tab 'Scala 2 and 3' for=volatile %} @volatile var totalA = 0 val text = Future { @@ -509,6 +548,8 @@ text. text.foreach { txt => totalA += txt.count(_ == 'A') } +{% endtab %} +{% endtabs %} Above, the two callbacks may execute one after the other, in which case the variable `totalA` holds the expected value `18`. @@ -725,12 +766,16 @@ earlier. The for-comprehension above is translated into: +{% tabs for-translation %} +{% tab 'Scala 2 and 3' for=for-translation %} val purchase = usdQuote.flatMap { usd => - chfQuote - .withFilter(chf => isProfitable(usd, chf)) - .map(chf => connection.buy(amount, chf)) + chfQuote + .withFilter(chf => isProfitable(usd, chf)) + .map(chf => connection.buy(amount, chf)) } +{% endtab %} +{% endtabs %} which is a bit harder to grasp than the for-comprehension, but we analyze it to better understand the `flatMap` operation. @@ -767,11 +812,15 @@ amount. The `connection.buy` method takes an `amount` to buy and the expected future to contain `0` instead of the exception, we use the `recover` combinator: +{% tabs recover %} +{% tab 'Scala 2 and 3' for=recover %} val purchase: Future[Int] = rateQuote.map { quote => connection.buy(amount, quote) }.recover { case QuoteChangedException() => 0 } +{% endtab %} +{% endtabs %} The `recover` combinator creates a new future which holds the same result as the original future if it completed successfully. If it did @@ -796,6 +845,8 @@ the exception from this future, as in the following example which tries to print US dollar value, but prints the Swiss franc value in the case it fails to obtain the dollar value: +{% tabs fallback-to %} +{% tab 'Scala 2 and 3' for=fallback-to %} val usdQuote = Future { connection.getCurrentValue(USD) }.map { @@ -810,6 +861,8 @@ the case it fails to obtain the dollar value: val anyQuote = usdQuote.fallbackTo(chfQuote) anyQuote.foreach { println(_) } +{% endtab %} +{% endtabs %} The `andThen` combinator is used purely for side-effecting purposes. It returns a new future with exactly the same result as the current @@ -943,7 +996,11 @@ As seen with the global `ExecutionContext`, it is possible to notify an `Executi The implementation is however at the complete discretion of the `ExecutionContext`. While some `ExecutionContext` such as `ExecutionContext.global` implement `blocking` by means of a `ManagedBlocker`, some execution contexts such as the fixed thread pool: +{% tabs fixed-thread-pool %} +{% tab 'Scala 2 and 3' for=fixed-thread-pool %} ExecutionContext.fromExecutor(Executors.newFixedThreadPool(x)) +{% endtab %} +{% endtabs %} will do nothing, as shown in the following: @@ -968,7 +1025,11 @@ will do nothing, as shown in the following: Has the same effect as +{% tabs alternative %} +{% tab 'Scala 2 and 3' for=alternative %} Future { blockingStuff() } +{% endtab %} +{% endtabs %} The blocking code may also throw an exception. In this case, the exception is forwarded to the caller. @@ -1088,6 +1149,8 @@ Consider the following producer-consumer example, in which one computation produces a value and hands it off to another computation which consumes that value. This passing of the value is done using a promise. +{% tabs promises %} +{% tab 'Scala 2 and 3' for=promises %} import scala.concurrent.{ Future, Promise } import scala.concurrent.ExecutionContext.Implicits.global @@ -1106,6 +1169,8 @@ that value. This passing of the value is done using a promise. doSomethingWithResult() } } +{% endtab %} +{% endtabs %} Here, we create a promise and use its `future` method to obtain the `Future` that it completes. Then, we begin two asynchronous @@ -1188,6 +1253,8 @@ The method `completeWith` completes the promise with another future. After the future is completed, the promise gets completed with the result of that future as well. The following program prints `1`: +{% tabs promises-2 %} +{% tab 'Scala 2 and 3' for=promises-2 %} val f = Future { 1 } val p = Promise[Int]() @@ -1196,6 +1263,8 @@ the result of that future as well. The following program prints `1`: p.future.foreach { x => println(x) } +{% endtab %} +{% endtabs %} When failing a promise with an exception, three subtypes of `Throwable`s are handled specially. If the `Throwable` used to break the promise is diff --git a/_overviews/core/string-interpolation.md b/_overviews/core/string-interpolation.md index de1a968289..27c4aed480 100644 --- a/_overviews/core/string-interpolation.md +++ b/_overviews/core/string-interpolation.md @@ -15,8 +15,12 @@ permalink: /overviews/core/:title.html Starting in Scala 2.10.0, Scala offers a new mechanism to create strings from your data: String Interpolation. String Interpolation allows users to embed variable references directly in *processed* string literals. Here's an example: +{% tabs example-1 %} +{% tab 'Scala 2 and 3' for=example-1 %} val name = "James" println(s"Hello, $name") // Hello, James +{% endtab %} +{% endtabs %} In the above, the literal `s"Hello, $name"` is a *processed* string literal. This means that the compiler does some additional work to this literal. A processed string literal is denoted by a set of characters preceding the `"`. String interpolation @@ -30,28 +34,44 @@ Scala provides three string interpolation methods out of the box: `s`, `f` and Prepending `s` to any string literal allows the usage of variables directly in the string. You've already seen an example here: +{% tabs example-2 %} +{% tab 'Scala 2 and 3' for=example-2 %} val name = "James" println(s"Hello, $name") // Hello, James +{% endtab %} +{% endtabs %} Here `$name` is nested inside an `s` processed string. The `s` interpolator knows to insert the value of the `name` variable at this location in the string, resulting in the string `Hello, James`. With the `s` interpolator, any name that is in scope can be used within a string. String interpolators can also take arbitrary expressions. For example: +{% tabs example-3 %} +{% tab 'Scala 2 and 3' for=example-3 %} println(s"1 + 1 = ${1 + 1}") +{% endtab %} +{% endtabs %} will print the string `1 + 1 = 2`. Any arbitrary expression can be embedded in `${}`. For some special characters, it is necessary to escape them when embedded within a string. To represent an actual dollar sign you can double it `$$`, like here: +{% tabs example-4 %} +{% tab 'Scala 2 and 3' for=example-4 %} println(s"New offers starting at $$14.99") +{% endtab %} +{% endtabs %} which will print the string `New offers starting at $14.99`. Double quotes also need to be escaped. This can be done by using triple quotes as shown: +{% tabs example-5 %} +{% tab 'Scala 2 and 3' for=example-5 %} val person = """{"name":"James"}""" +{% endtab %} +{% endtabs %} which will produce the string `{"name":"James"}` when printed. @@ -60,9 +80,13 @@ which will produce the string `{"name":"James"}` when printed. Prepending `f` to any string literal allows the creation of simple formatted strings, similar to `printf` in other languages. When using the `f` interpolator, all variable references should be followed by a `printf`-style format string, like `%d`. Let's look at an example: +{% tabs example-6 %} +{% tab 'Scala 2 and 3' for=example-6 %} val height = 1.9d val name = "James" println(f"$name%s is $height%2.2f meters tall") // James is 1.90 meters tall +{% endtab %} +{% endtabs %} The `f` interpolator is typesafe. If you try to pass a format string that only works for integers but pass a double, the compiler will issue an error. For example: @@ -106,15 +130,23 @@ definition a formatter of `%s` (`String`) is assumed. The raw interpolator is similar to the `s` interpolator except that it performs no escaping of literals within the string. Here's an example processed string: +{% tabs example-7 %} +{% tab 'Scala 2 and 3' for=example-7 %} scala> s"a\nb" res0: String = a b +{% endtab %} +{% endtabs %} Here the `s` string interpolator replaced the characters `\n` with a return character. The `raw` interpolator will not do that. +{% tabs example-8 %} +{% tab 'Scala 2 and 3' for=example-8 %} scala> raw"a\nb" res1: String = a\nb +{% endtab %} +{% endtabs %} The raw interpolator is useful when you want to avoid having expressions like `\n` turn into a return character. @@ -124,7 +156,11 @@ In addition to the three default string interpolators, users can define their ow In Scala, all processed string literals are simple code transformations. Anytime the compiler encounters a string literal of the form: +{% tabs example-9 %} +{% tab 'Scala 2 and 3' for=example-9 %} id"string content" +{% endtab %} +{% endtabs %} it transforms it into a method call (`id`) on an instance of [StringContext](https://www.scala-lang.org/api/current/scala/StringContext.html). This method can also be available on implicit scope. diff --git a/_overviews/scala3-book/control-structures.md b/_overviews/scala3-book/control-structures.md index 736501a30b..5ace74f351 100644 --- a/_overviews/scala3-book/control-structures.md +++ b/_overviews/scala3-book/control-structures.md @@ -393,6 +393,8 @@ do You can also use `for` loops with a `Map`. For example, given this `Map` of state abbreviations and their full names: +{% tabs map %} +{% tab 'Scala 2 and 3' for=map %} ```scala val states = Map( "AK" -> "Alaska", @@ -400,6 +402,8 @@ val states = Map( "AR" -> "Arizona" ) ``` +{% endtab %} +{% endtabs %} You can print the keys and values using `for`, like this: @@ -491,9 +495,13 @@ NOTE: This is a place where it would be great to have a TIP or NOTE block: While the intent of this section is to demonstrate `for` expressions, it can help to know that the `for` expression shown is equivalent to this `map` method call: +{% tabs map-call %} +{% tab 'Scala 2 and 3' for=map-call %} ```scala val list = (10 to 12).map(i => i * 2) ``` +{% endtab %} +{% endtabs %} `for` expressions can be used any time you need to traverse all of the elements in a collection and apply an algorithm to those elements to create a new list. @@ -831,6 +839,8 @@ The first one checks whether the given value is either the integer `0`, an empt In the default case, we return `true` for any other value. These examples show how this method works: +{% tabs is-truthy-call %} +{% tab 'Scala 2 and 3' for=is-truthy-call %} ```scala isTruthy(0) // false isTruthy(false) // false @@ -839,6 +849,8 @@ isTruthy(1) // true isTruthy(" ") // true isTruthy(2F) // true ``` +{% endtab %} +{% endtabs %} Using a `match` expression as the body of a method is a very common use. diff --git a/_overviews/scala3-book/taste-hello-world.md b/_overviews/scala3-book/taste-hello-world.md index 4a65bfb0bd..b43512cdf7 100644 --- a/_overviews/scala3-book/taste-hello-world.md +++ b/_overviews/scala3-book/taste-hello-world.md @@ -102,9 +102,13 @@ In our next example let's ask for the user's name before we greet them! There are several ways to read input from a command-line, but a simple way is to use the `readLine` method in the _scala.io.StdIn_ object. To use it, you need to first import it, like this: +{% tabs import-readline %} +{% tab 'Scala 2 and 3' for=import-readline %} ```scala import scala.io.StdIn.readLine ``` +{% endtab %} +{% endtabs %} To demonstrate how this works, let’s create a little example. Put this source code in a file named _helloInteractive.scala_: @@ -176,9 +180,13 @@ Hello, Alvin Alexander! As you saw in this application, sometimes certain methods, or other kinds of definitions that we'll see later, are not available unless you use an `import` clause like so: +{% tabs import-readline-2 %} +{% tab 'Scala 2 and 3' for=import-readline-2 %} ```scala import scala.io.StdIn.readLine ``` +{% endtab %} +{% endtabs %} Imports help you write code in a few ways: - you can put code in multiple files, to help avoid clutter, and to help navigate large projects. diff --git a/_tour/basics.md b/_tour/basics.md index c53b393c68..8118507637 100644 --- a/_tour/basics.md +++ b/_tour/basics.md @@ -25,41 +25,63 @@ _Scastie_ is integrated with some of the code examples in this documentation; if ## Expressions Expressions are computable statements: + +{% tabs expression %} +{% tab 'Scala 2 and 3' for=expression %} ```scala mdoc 1 + 1 ``` +{% endtab %} +{% endtabs %} + You can output the results of expressions using `println`: +{% tabs println %} +{% tab 'Scala 2 and 3' for=println %} ```scala mdoc println(1) // 1 println(1 + 1) // 2 println("Hello!") // Hello! println("Hello," + " world!") // Hello, world! ``` +{% endtab %} +{% endtabs %} ### Values You can name the results of expressions using the `val` keyword: +{% tabs val %} +{% tab 'Scala 2 and 3' for=val %} ```scala mdoc val x = 1 + 1 println(x) // 2 ``` +{% endtab %} +{% endtabs %} Named results, such as `x` here, are called values. Referencing a value does not re-compute it. Values cannot be re-assigned: +{% tabs val-error %} +{% tab 'Scala 2 and 3' for=val-error %} ```scala mdoc:fail x = 3 // This does not compile. ``` +{% endtab %} +{% endtabs %} The type of a value can be omitted and [inferred](https://docs.scala-lang.org/tour/type-inference.html), or it can be explicitly stated: +{% tabs type-inference %} +{% tab 'Scala 2 and 3' for=type-inference %} ```scala mdoc:nest val x: Int = 1 + 1 ``` +{% endtab %} +{% endtabs %} Notice how the type declaration `Int` comes after the identifier `x`. You also need a `:`. @@ -67,17 +89,25 @@ Notice how the type declaration `Int` comes after the identifier `x`. You also n Variables are like values, except you can re-assign them. You can define a variable with the `var` keyword. +{% tabs var %} +{% tab 'Scala 2 and 3' for=var %} ```scala mdoc:nest var x = 1 + 1 x = 3 // This compiles because "x" is declared with the "var" keyword. println(x * x) // 9 ``` +{% endtab %} +{% endtabs %} As with values, the type of a variable can be omitted and [inferred](https://docs.scala-lang.org/tour/type-inference.html), or it can be explicitly stated: +{% tabs type-inference-2 %} +{% tab 'Scala 2 and 3' for=type-inference-2 %} ```scala mdoc:nest var x: Int = 1 + 1 ``` +{% endtab %} +{% endtabs %} ## Blocks @@ -86,12 +116,16 @@ You can combine expressions by surrounding them with `{}`. We call this a block. The result of the last expression in the block is the result of the overall block, too: +{% tabs blocks %} +{% tab 'Scala 2 and 3' for=blocks %} ```scala mdoc println({ val x = 1 + 1 x + 1 }) // 3 ``` +{% endtab %} +{% endtabs %} ## Functions @@ -99,32 +133,48 @@ Functions are expressions that have parameters, and take arguments. You can define an anonymous function (i.e., a function that has no name) that returns a given integer plus one: +{% tabs anonymous-function %} +{% tab 'Scala 2 and 3' for=anonymous-function %} ```scala mdoc (x: Int) => x + 1 ``` +{% endtab %} +{% endtabs %} On the left of `=>` is a list of parameters. On the right is an expression involving the parameters. You can also name functions: +{% tabs named-function %} +{% tab 'Scala 2 and 3' for=named-function %} ```scala mdoc val addOne = (x: Int) => x + 1 println(addOne(1)) // 2 ``` +{% endtab %} +{% endtabs %} A function can have multiple parameters: +{% tabs multiple-parameters %} +{% tab 'Scala 2 and 3' for=multiple-parameters %} ```scala mdoc val add = (x: Int, y: Int) => x + y println(add(1, 2)) // 3 ``` +{% endtab %} +{% endtabs %} Or it can have no parameters at all: +{% tabs no-parameters %} +{% tab 'Scala 2 and 3' for=no-parameters %} ```scala mdoc val getTheAnswer = () => 42 println(getTheAnswer()) // 42 ``` +{% endtab %} +{% endtabs %} ## Methods @@ -132,26 +182,38 @@ Methods look and behave very similar to functions, but there are a few key diffe Methods are defined with the `def` keyword. `def` is followed by a name, parameter list(s), a return type, and a body: +{% tabs method %} +{% tab 'Scala 2 and 3' for=method %} ```scala mdoc:nest def add(x: Int, y: Int): Int = x + y println(add(1, 2)) // 3 ``` +{% endtab %} +{% endtabs %} Notice how the return type `Int` is declared _after_ the parameter list and a `:`. A method can take multiple parameter lists: +{% tabs multiple-parameter-lists %} +{% tab 'Scala 2 and 3' for=multiple-parameter-lists %} ```scala mdoc def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier println(addThenMultiply(1, 2)(3)) // 9 ``` +{% endtab %} +{% endtabs %} Or no parameter lists at all: +{% tabs no-parameter-lists %} +{% tab 'Scala 2 and 3' for=no-parameter-lists %} ```scala mdoc def name: String = System.getProperty("user.name") println("Hello, " + name + "!") ``` +{% endtab %} +{% endtabs %} There are some other differences, but for now, you can think of methods as something similar to functions. @@ -238,17 +300,25 @@ Scala has a special type of class called a "case" class. By default, instances o You can define case classes with the `case class` keywords: +{% tabs case-class-definition %} +{% tab 'Scala 2 and 3' for=case-class-definition %} ```scala mdoc case class Point(x: Int, y: Int) ``` +{% endtab %} +{% endtabs %} You can instantiate case classes without the `new` keyword: +{% tabs case-class-creation %} +{% tab 'Scala 2 and 3' for=case-class-creation %} ```scala mdoc val point = Point(1, 2) val anotherPoint = Point(1, 2) val yetAnotherPoint = Point(2, 2) ``` +{% endtab %} +{% endtabs %} Instances of case classes are compared by value, not by reference: @@ -324,12 +394,16 @@ object IdFactory: You can access an object by referring to its name: +{% tabs id-factory-usage %} +{% tab 'Scala 2 and 3' for=id-factory-usage %} ```scala mdoc val newId: Int = IdFactory.create() println(newId) // 1 val newerId: Int = IdFactory.create() println(newerId) // 2 ``` +{% endtab %} +{% endtabs %} We will cover objects in depth [later](singleton-objects.html). diff --git a/_tour/pattern-matching.md b/_tour/pattern-matching.md index 3a6bdbce5d..4ef988529b 100644 --- a/_tour/pattern-matching.md +++ b/_tour/pattern-matching.md @@ -78,6 +78,8 @@ This match expression has a type String because all of the cases return String. Case classes are especially useful for pattern matching. +{% tabs notification %} +{% tab 'Scala 2 and 3' for=notification %} ```scala mdoc sealed trait Notification @@ -87,6 +89,9 @@ case class SMS(caller: String, message: String) extends Notification case class VoiceRecording(contactName: String, link: String) extends Notification ``` +{% endtab %} +{% endtabs %} + `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 %} diff --git a/_tour/traits.md b/_tour/traits.md index 031de856bc..cf12b763be 100644 --- a/_tour/traits.md +++ b/_tour/traits.md @@ -17,9 +17,13 @@ Traits are used to share interfaces and fields between classes. They are similar ## Defining a trait A minimal trait is simply the keyword `trait` and an identifier: +{% tabs trait-hair-color %} +{% tab 'Scala 2 and 3' for=trait-hair-color %} ```scala mdoc trait HairColor ``` +{% endtab %} +{% endtabs %} Traits become especially useful as generic types and with abstract methods.