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`.