Skip to content

Tabs for Scala 2/3 in some pages #2455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 85 additions & 47 deletions _overviews/collections-2.13/maps.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)`<br>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`<br>or `ms - k` |The map containing all mappings of `ms` except for any mapping of key `k`.|
| `ms removeAll ks`<br>or `ms -- ks` |The map containing all mappings of `ms` except for any mapping with a key in `ks`.|
| **Removals:** | |
| `ms.remove(k)`<br>or `ms - k` |The map containing all mappings of `ms` except for any mapping of key `k`.|
| `ms.removeAll(ks)`<br>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)`<br>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)`<br>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`<br>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`<br>or `ms -= k` |Removes mapping with key `k` from ms as a side effect and returns `ms` itself.|
| `ms subtractAll ks`<br>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)`<br>or `ms -= k` |Removes mapping with key `k` from ms as a side effect and returns `ms` itself.|
| `ms.subtractAll(ks)`<br>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`.

Expand All @@ -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.
Expand All @@ -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 %}
144 changes: 112 additions & 32 deletions _overviews/core/string-interpolation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
<console>: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"
<console>: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
Expand Down Expand Up @@ -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`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of mentioning both Scala 2 and Scala 3 in this sentence, you could add a specialised line of text to either tab

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't really "look well" if I do that, IMO.

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