diff --git a/_overviews/collections-2.13/maps.md b/_overviews/collections-2.13/maps.md
index 8dff664b27..9fe1af00d7 100644
--- a/_overviews/collections-2.13/maps.md
+++ b/_overviews/collections-2.13/maps.md
@@ -24,68 +24,87 @@ The fundamental operations on maps are similar to those on sets. They are summar
### Operations in Class Map ###
-| WHAT IT IS | WHAT IT DOES |
-| ------ | ------ |
-| **Lookups:** | |
-| `ms get k` |The value associated with key `k` in map `ms` as an option, `None` if not found.|
-| `ms(k)` |(or, written out, `ms apply k`) The value associated with key `k` in map `ms`, or exception if not found.|
-| `ms getOrElse (k, d)` |The value associated with key `k` in map `ms`, or the default value `d` if not found.|
-| `ms contains k` |Tests whether `ms` contains a mapping for key `k`.|
-| `ms isDefinedAt k` |Same as `contains`. |
-| **Subcollections:** | |
+| WHAT IT IS | WHAT IT DOES |
+| ------ | ------ |
+| **Lookups:** | |
+| `ms.get(k)` |The value associated with key `k` in map `ms` as an option, `None` if not found.|
+| `ms(k)` |(or, written out, `ms.apply(k)`) The value associated with key `k` in map `ms`, or exception if not found.|
+| `ms.getOrElse(k, d)` |The value associated with key `k` in map `ms`, or the default value `d` if not found.|
+| `ms.contains(k)` |Tests whether `ms` contains a mapping for key `k`.|
+| `ms.isDefinedAt(k)` |Same as `contains`. |
+| **Subcollections:** | |
| `ms.keys` |An iterable containing each key in `ms`. |
| `ms.keySet` |A set containing each key in `ms`. |
| `ms.keysIterator` |An iterator yielding each key in `ms`. |
| `ms.values` |An iterable containing each value associated with a key in `ms`.|
| `ms.valuesIterator` |An iterator yielding each value associated with a key in `ms`.|
-| **Transformation:** | |
-| `ms.view filterKeys p` |A map view containing only those mappings in `ms` where the key satisfies predicate `p`.|
-| `ms.view mapValues f` |A map view resulting from applying function `f` to each value associated with a key in `ms`.|
+| **Transformation:** | |
+| `ms.view.filterKeys(p)` |A map view containing only those mappings in `ms` where the key satisfies predicate `p`.|
+| `ms.view.mapValues(f)` |A map view resulting from applying function `f` to each value associated with a key in `ms`.|
Immutable maps support in addition operations to add and remove mappings by returning new `Map`s, as summarized in the following table.
### Operations in Class immutable.Map ###
-| WHAT IT IS | WHAT IT DOES |
-| ------ | ------ |
-| **Additions and Updates:**| |
+| WHAT IT IS | WHAT IT DOES |
+| ------ | ------ |
+| **Additions and Updates:**| |
| `ms.updated(k, v)`
or `ms + (k -> v)` |The map containing all mappings of `ms` as well as the mapping `k -> v` from key `k` to value `v`.|
-| **Removals:** | |
-| `ms remove k`
or `ms - k` |The map containing all mappings of `ms` except for any mapping of key `k`.|
-| `ms removeAll ks`
or `ms -- ks` |The map containing all mappings of `ms` except for any mapping with a key in `ks`.|
+| **Removals:** | |
+| `ms.remove(k)`
or `ms - k` |The map containing all mappings of `ms` except for any mapping of key `k`.|
+| `ms.removeAll(ks)`
or `ms -- ks` |The map containing all mappings of `ms` except for any mapping with a key in `ks`.|
Mutable maps support in addition the operations summarized in the following table.
### Operations in Class mutable.Map ###
-| WHAT IT IS | WHAT IT DOES |
-| ------ | ------ |
-| **Additions and Updates:**| |
-| `ms(k) = v` |(Or, written out, `ms.update(k, v)`). Adds mapping from key `k` to value `v` to map ms as a side effect, overwriting any previous mapping of `k`.|
-| `ms.addOne(k -> v)`
or `ms += (k -> v)` |Adds mapping from key `k` to value `v` to map `ms` as a side effect and returns `ms` itself.|
+| WHAT IT IS | WHAT IT DOES |
+| ------ | ------ |
+| **Additions and Updates:** | |
+| `ms(k) = v` |(Or, written out, `ms.update(k, v)`). Adds mapping from key `k` to value `v` to map ms as a side effect, overwriting any previous mapping of `k`.|
+| `ms.addOne(k -> v)`
or `ms += (k -> v)` |Adds mapping from key `k` to value `v` to map `ms` as a side effect and returns `ms` itself.|
| `ms addAll xvs`
or `ms ++= kvs` |Adds all mappings in `kvs` to `ms` as a side effect and returns `ms` itself.|
-| `ms.put(k, v)` |Adds mapping from key `k` to value `v` to `ms` and returns any value previously associated with `k` as an option.|
-| `ms getOrElseUpdate (k, d)`|If key `k` is defined in map `ms`, return its associated value. Otherwise, update `ms` with the mapping `k -> d` and return `d`.|
-| **Removals:**| |
-| `ms subtractOne k`
or `ms -= k` |Removes mapping with key `k` from ms as a side effect and returns `ms` itself.|
-| `ms subtractAll ks`
or `ms --= ks` |Removes all keys in `ks` from `ms` as a side effect and returns `ms` itself.|
-| `ms remove k` |Removes any mapping with key `k` from `ms` and returns any value previously associated with `k` as an option.|
-| `ms filterInPlace p` |Keeps only those mappings in `ms` that have a key satisfying predicate `p`.|
-| `ms.clear()` |Removes all mappings from `ms`. |
-| **Transformation:** | |
-| `ms mapValuesInPlace f` |Transforms all associated values in map `ms` with function `f`.|
-| **Cloning:** | |
-| `ms.clone` |Returns a new mutable map with the same mappings as `ms`.|
+| `ms.put(k, v)` |Adds mapping from key `k` to value `v` to `ms` and returns any value previously associated with `k` as an option.|
+| `ms.getOrElseUpdate(k, d)` |If key `k` is defined in map `ms`, return its associated value. Otherwise, update `ms` with the mapping `k -> d` and return `d`.|
+| **Removals:** | |
+| `ms.subtractOne(k)`
or `ms -= k` |Removes mapping with key `k` from ms as a side effect and returns `ms` itself.|
+| `ms.subtractAll(ks)`
or `ms --= ks` |Removes all keys in `ks` from `ms` as a side effect and returns `ms` itself.|
+| `ms.remove(k)` |Removes any mapping with key `k` from `ms` and returns any value previously associated with `k` as an option.|
+| `ms.filterInPlace(p)` |Keeps only those mappings in `ms` that have a key satisfying predicate `p`.|
+| `ms.clear()` |Removes all mappings from `ms`. |
+| **Transformation:** | |
+| `ms.mapValuesInPlace(f)` |Transforms all associated values in map `ms` with function `f`.|
+| **Cloning:** | |
+| `ms.clone` |Returns a new mutable map with the same mappings as `ms`.|
The addition and removal operations for maps mirror those for sets. A mutable map `m` is usually updated "in place", using the two variants `m(key) = value` or `m += (key -> value)`. There is also the variant `m.put(key, value)`, which returns an `Option` value that contains the value previously associated with `key`, or `None` if the `key` did not exist in the map before.
The `getOrElseUpdate` is useful for accessing maps that act as caches. Say you have an expensive computation triggered by invoking a function `f`:
- scala> def f(x: String) = {
- println("taking my time."); sleep(100)
- x.reverse }
- f: (x: String)String
+{% tabs expensive-computation-reverse class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=expensive-computation-reverse %}
+```scala
+scala> def f(x: String): String = {
+ println("taking my time."); Thread.sleep(100)
+ x.reverse
+ }
+f: (x: String)String
+```
+{% endtab %}
+
+{% tab 'Scala 3' for=expensive-computation-reverse %}
+```scala
+scala> def f(x: String): String =
+ println("taking my time."); Thread.sleep(100)
+ x.reverse
+
+def f(x: String): String
+```
+{% endtab %}
+
+{% endtabs %}
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`.
@@ -94,7 +113,7 @@ Assume further that `f` has no side-effects, so invoking it again with the same
You can now create a more efficient caching version of the `f` function:
- scala> def cachedF(s: String) = cache.getOrElseUpdate(s, f(s))
+ scala> def cachedF(s: String): String = cache.getOrElseUpdate(s, f(s))
cachedF: (s: String)String
scala> cachedF("abc")
taking my time.
@@ -104,10 +123,29 @@ You can now create a more efficient caching version of the `f` function:
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:
- def cachedF(arg: String) = cache get arg match {
- case Some(result) => result
- case None =>
- val result = f(x)
- cache(arg) = result
- result
- }
+{% tabs cacheF class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=cacheF %}
+```scala
+def cachedF(arg: String): String = cache.get(arg) match {
+ case Some(result) => result
+ case None =>
+ val result = f(x)
+ cache(arg) = result
+ result
+}
+```
+{% endtab %}
+
+{% tab 'Scala 3' for=cacheF %}
+```scala
+def cachedF(arg: String): String = cache.get(arg) match
+ case Some(result) => result
+ case None =>
+ val result = f(x)
+ cache(arg) = result
+ result
+```
+{% endtab %}
+
+{% endtabs %}
diff --git a/_overviews/core/string-interpolation.md b/_overviews/core/string-interpolation.md
index fab22642b2..de1a968289 100644
--- a/_overviews/core/string-interpolation.md
+++ b/_overviews/core/string-interpolation.md
@@ -67,14 +67,36 @@ interpolator, all variable references should be followed by a `printf`-style for
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:
- val height: Double = 1.9d
+{% tabs f-interpolator-error class=tabs-scala-version %}
- scala> f"$height%4d"
- :9: error: type mismatch;
- found : Double
- required: Int
- f"$height%4d"
- ^
+{% tab 'Scala 2' for=f-interpolator-error %}
+```scala
+val height: Double = 1.9d
+
+scala> f"$height%4d"
+:9: error: type mismatch;
+ found : Double
+ required: Int
+ f"$height%4d"
+ ^
+```
+{% endtab %}
+
+{% tab 'Scala 3' for=f-interpolator-error %}
+```scala
+val height: Double = 1.9d
+
+scala> f"$height%4d"
+-- Error: ----------------------------------------------------------------------
+1 |f"$height%4d"
+ | ^^^^^^
+ | Found: (height : Double), Required: Int, Long, Byte, Short, BigInt
+1 error found
+
+```
+{% endtab %}
+
+{% endtabs %}
The `f` interpolator makes use of the string format utilities available from Java. The formats allowed after the `%` character are outlined in the
[Formatter javadoc](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html#detail). If there is no `%` character after a variable
@@ -105,42 +127,100 @@ In Scala, all processed string literals are simple code transformations. Anyti
id"string content"
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. To define our own string interpolation, we simply need to create an implicit class that adds a new method
-to `StringContext`. Here's an example:
+This method can also be available on implicit scope.
+To define our own string interpolation, we need to create an implicit class (Scala 2) or an `extension` method (Scala 3) that adds a new method to `StringContext`.
+Here's an example:
- // Note: We extends AnyVal to prevent runtime instantiation. See
- // value class guide for more info.
- implicit class JsonHelper(val sc: StringContext) extends AnyVal {
- def json(args: Any*): JSONObject = sys.error("TODO - IMPLEMENT")
- }
+{% tabs json-definition-and-usage class=tabs-scala-version %}
- def giveMeSomeJson(x: JSONObject): Unit = ...
+{% tab 'Scala 2' for=json-definition-and-usage %}
+```scala
+// Note: We extends AnyVal to prevent runtime instantiation. See
+// value class guide for more info.
+implicit class JsonHelper(val sc: StringContext) extends AnyVal {
+ def json(args: Any*): JSONObject = sys.error("TODO - IMPLEMENT")
+}
- giveMeSomeJson(json"{ name: $name, id: $id }")
+def giveMeSomeJson(x: JSONObject): Unit = ...
+
+giveMeSomeJson(json"{ name: $name, id: $id }")
+```
+{% endtab %}
+
+{% tab 'Scala 3' for=json-definition-and-usage %}
+```scala
+extension (sc: StringContext)
+ def json(args: Any*): JSONObject = sys.error("TODO - IMPLEMENT")
+
+def giveMeSomeJson(x: JSONObject): Unit = ...
+
+giveMeSomeJson(json"{ name: $name, id: $id }")
+```
+{% endtab %}
+
+{% endtabs %}
In this example, we're attempting to create a JSON literal syntax using string interpolation. The `JsonHelper` implicit class must be in scope to use this syntax, and the json method would need a complete implementation. However, the result of such a formatted string literal would not be a string, but a `JSONObject`.
When the compiler encounters the literal `json"{ name: $name, id: $id }"` it rewrites it to the following expression:
- new StringContext("{ name: ", ", id: ", " }").json(name, id)
+{% tabs extension-desugaring class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=extension-desugaring %}
+```scala
+new StringContext("{ name: ", ", id: ", " }").json(name, id)
+```
The implicit class is then used to rewrite it to the following:
- new JsonHelper(new StringContext("{ name: ", ", id: ", " }")).json(name, id)
-
-So, the `json` method has access to the raw pieces of strings and each expression as a value. A simple (buggy) implementation of this method could be:
-
- implicit class JsonHelper(val sc: StringContext) extends AnyVal {
- def json(args: Any*): JSONObject = {
- val strings = sc.parts.iterator
- val expressions = args.iterator
- var buf = new StringBuilder(strings.next())
- while(strings.hasNext) {
- buf.append(expressions.next())
- buf.append(strings.next())
- }
- parseJson(buf)
- }
+```scala
+new JsonHelper(new StringContext("{ name: ", ", id: ", " }")).json(name, id)
+```
+{% endtab %}
+
+{% tab 'Scala 3' for=extension-desugaring %}
+```scala
+StringContext("{ name: ", ", id: ", " }").json(name, id)
+```
+{% endtab %}
+
+{% endtabs %}
+
+So, the `json` method has access to the raw pieces of strings and each expression as a value. A simplified (buggy) implementation of this method could be:
+
+{% tabs json-fake-implementation class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=json-fake-implementation %}
+```scala
+implicit class JsonHelper(val sc: StringContext) extends AnyVal {
+ def json(args: Any*): JSONObject = {
+ val strings = sc.parts.iterator
+ val expressions = args.iterator
+ var buf = new StringBuilder(strings.next())
+ while (strings.hasNext) {
+ buf.append(expressions.next())
+ buf.append(strings.next())
}
+ parseJson(buf)
+ }
+}
+```
+{% endtab %}
+
+{% tab 'Scala 3' for=json-fake-implementation %}
+```scala
+extension (sc: StringContext)
+ def json(args: Any*): JSONObject =
+ val strings = sc.parts.iterator
+ val expressions = args.iterator
+ var buf = new StringBuilder(strings.next())
+ while strings.hasNext do
+ buf.append(expressions.next())
+ buf.append(strings.next())
+ parseJson(buf)
+```
+{% endtab %}
+
+{% endtabs %}
Each of the string portions of the processed string are exposed in the `StringContext`'s `parts` member. Each of the expression values is passed into the `json` method's `args` parameter. The `json` method takes this and generates a big string which it then parses into JSON. A more sophisticated implementation could avoid having to generate this string and simply construct the JSON directly from the raw strings and expression values.
diff --git a/_tour/traits.md b/_tour/traits.md
index 6500490b7d..031de856bc 100644
--- a/_tour/traits.md
+++ b/_tour/traits.md
@@ -22,17 +22,36 @@ trait HairColor
```
Traits become especially useful as generic types and with abstract methods.
+
+{% tabs trait-iterator-definition class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=trait-iterator-definition %}
```scala mdoc
trait Iterator[A] {
def hasNext: Boolean
def next(): A
}
```
+{% endtab %}
+
+{% tab 'Scala 3' for=trait-iterator-definition %}
+```scala
+trait Iterator[A]:
+ def hasNext: Boolean
+ def next(): A
+```
+{% endtab %}
+
+{% endtabs %}
Extending the `trait Iterator[A]` requires a type `A` and implementations of the methods `hasNext` and `next`.
## Using traits
Use the `extends` keyword to extend a trait. Then implement any abstract members of the trait using the `override` keyword:
+
+{% tabs trait-intiterator-definition class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=trait-intiterator-definition %}
```scala mdoc:nest
trait Iterator[A] {
def hasNext: Boolean
@@ -51,15 +70,46 @@ class IntIterator(to: Int) extends Iterator[Int] {
}
}
+val iterator = new IntIterator(10)
+iterator.next() // returns 0
+iterator.next() // returns 1
+```
+{% endtab %}
+
+{% tab 'Scala 3' for=trait-intiterator-definition %}
+```scala
+trait Iterator[A]:
+ def hasNext: Boolean
+ def next(): A
+
+class IntIterator(to: Int) extends Iterator[Int]:
+ private var current = 0
+ override def hasNext: Boolean = current < to
+ override def next(): Int =
+ if hasNext then
+ val t = current
+ current += 1
+ t
+ else
+ 0
+end IntIterator
val iterator = new IntIterator(10)
iterator.next() // returns 0
iterator.next() // returns 1
```
+{% endtab %}
+
+{% endtabs %}
+
This `IntIterator` class takes a parameter `to` as an upper bound. It `extends Iterator[Int]` which means that the `next` method must return an Int.
## Subtyping
Where a given trait is required, a subtype of the trait can be used instead.
+
+{% tabs trait-pet-example class=tabs-scala-version %}
+
+{% tab 'Scala 2' for=trait-pet-example %}
```scala mdoc
import scala.collection.mutable.ArrayBuffer
@@ -78,6 +128,30 @@ animals.append(dog)
animals.append(cat)
animals.foreach(pet => println(pet.name)) // Prints Harry Sally
```
+{% endtab %}
+
+{% tab 'Scala 3' for=trait-pet-example %}
+```scala
+import scala.collection.mutable.ArrayBuffer
+
+trait Pet:
+ val name: String
+
+class Cat(val name: String) extends Pet
+class Dog(val name: String) extends Pet
+
+val dog = Dog("Harry")
+val cat = Cat("Sally")
+
+val animals = ArrayBuffer.empty[Pet]
+animals.append(dog)
+animals.append(cat)
+animals.foreach(pet => println(pet.name)) // Prints Harry Sally
+```
+{% endtab %}
+
+{% endtabs %}
+
The `trait Pet` has an abstract field `name` that gets implemented by Cat and Dog in their constructors. On the last line, we call `pet.name`, which must be implemented in any subtype of the trait `Pet`.