Skip to content

Suggestions in the Scala 3 book (part 4) #2079

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 1 commit into from
Jun 15, 2021
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
2 changes: 1 addition & 1 deletion _overviews/scala3-book/ca-context-bounds.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ next-page: ca-given-imports
- TODO: define "synthesized" and "synthesized arguments"
{% endcomment %}

In many situations the name of a _context parameter_ doesn’t have to be mentioned explicitly, since it’s only used in synthesized arguments for other context parameters.
In many situations the name of a _context parameter_ doesn’t have to be mentioned explicitly, since it’s only used by the compiler in synthesized arguments for other context parameters.
In that case you don’t have to define a parameter name, and can just provide the parameter type.


Expand Down
2 changes: 1 addition & 1 deletion _overviews/scala3-book/ca-given-using-clauses.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ The Scala compiler thus performs **term inference**.
In our call to `renderWidget(List("cart"))` the Scala compiler will see that there is a term of type `Config` in scope (the `c`) and automatically provide it to `renderWidget`.
So the program is equivalent to the one above.

In fact, since we do not need to refer to `c` in our implementation of `renderWebsite` anymore, we can even omit it in the signature:
In fact, since we do not need to refer to `c` in our implementation of `renderWebsite` anymore, we can even omit its name in the signature:

```scala
// no need to come up with a parameter name
Expand Down
11 changes: 7 additions & 4 deletions _overviews/scala3-book/ca-implicit-conversions.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ next-page: ca-summary
---


Implicit conversions are defined by `given` instances of the _scala.Conversion_ class.
For example, not accounting for possible conversion errors, this code defines an an implicit conversion from `String` to `Int`:
Implicit conversions are defined by `given` instances of the `scala.Conversion` class.
For example, not accounting for possible conversion errors, this code defines an implicit conversion from `String` to `Int`:

```scala
given Conversion[String, Int] with
Expand All @@ -34,10 +34,13 @@ def plus1(i: Int) = i + 1
plus1("1")
```

> Note the clause `import scala.language.implicitConversions` at the beginning,
> to enable implicit conversions in the file.

## Discussion

The Predef package contains “auto-boxing” conversions that map primitive number types to subclasses of _java.lang.Number_.
For instance, the conversion from `Int` to _java.lang.Integer_ can be defined as follows:
The Predef package contains “auto-boxing” conversions that map primitive number types to subclasses of `java.lang.Number`.
For instance, the conversion from `Int` to `java.lang.Integer` can be defined as follows:

```scala
given int2Integer: Conversion[Int, java.lang.Integer] =
Expand Down
36 changes: 19 additions & 17 deletions _overviews/scala3-book/collections-classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ When you need more flexibility, see these pages at the end of this section for m

Looking at Scala collections from a high level, there are three main categories to choose from:

- **Sequences** are a linear collection of elements and may be _indexed_ (like an array) or _linear_ (like a linked list)
- **Sequences** are a sequential collection of elements and may be _indexed_ (like an array) or _linear_ (like a linked list)
- **Maps** contain a collection of key/value pairs, like a Java `Map`, Python dictionary, or Ruby `Hash`
- **Sets** are an unordered sequence of unique elements
- **Sets** are an unordered collection of unique elements

All of those are basic types, and have subtypes for specific purposes, such as concurrency, caching, and streaming.
In addition to those three main categories, there are other useful collection types, including ranges, stacks, and queues.
Expand Down Expand Up @@ -74,7 +74,7 @@ The main collections you’ll use on a regular basis are:
| `LazyList` | ✓ | | A lazy immutable linked list, its elements are computed only when they’re needed; Good for large or infinite sequences. |
| `ArrayBuffer` | | ✓ | The go-to type for a mutable, indexed sequence |
| `ListBuffer` | | ✓ | Used when you want a mutable `List`; typically converted to a `List` |
| `Map` | ✓ | ✓ | An iterable sequence that consists of pairs of keys and values. |
| `Map` | ✓ | ✓ | An iterable collection that consists of pairs of keys and values. |
| `Set` | ✓ | ✓ | An iterable collection with no duplicate elements |

As shown, `Map` and `Set` come in both immutable and mutable versions.
Expand Down Expand Up @@ -109,7 +109,7 @@ For example, if you need an immutable, indexed collection, in general you should
Conversely, if you need a mutable, indexed collection, use an `ArrayBuffer`.

> `List` and `Vector` are often used when writing code in a functional style.
> `ArrayBuffer` is commonly used when writing code in a mutable style.
> `ArrayBuffer` is commonly used when writing code in an imperative style.
> `ListBuffer` is used when you’re mixing styles, such as building a list.

The next several sections briefly demonstrate the `List`, `Vector`, and `ArrayBuffer` types.
Expand Down Expand Up @@ -291,7 +291,7 @@ val nums = Vector(1, 2, 3, 4, 5)

val strings = Vector("one", "two")

case class Person(val name: String)
case class Person(name: String)
val people = Vector(
Person("Bert"),
Person("Ernie"),
Expand Down Expand Up @@ -383,10 +383,9 @@ Or if you prefer methods with textual names you can also use `append`, `appendAl
Here are some examples of `+=` and `++=`:

```scala
var nums = ArrayBuffer(1, 2, 3) // ArrayBuffer(1, 2, 3)
val nums = ArrayBuffer(1, 2, 3) // ArrayBuffer(1, 2, 3)
nums += 4 // ArrayBuffer(1, 2, 3, 4)
nums += (5, 6) // ArrayBuffer(1, 2, 3, 4, 5, 6)
nums ++= List(7, 8) // ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8)
nums ++= List(5, 6) // ArrayBuffer(1, 2, 3, 4, 5, 6)
```

### Removing elements from an ArrayBuffer
Expand Down Expand Up @@ -415,7 +414,7 @@ a.update(0, 10) // ArrayBuffer(10, 2, 50, 4)

## Maps

A `Map` is an iterable sequence that consists of pairs of keys and values.
A `Map` is an iterable collection that consists of pairs of keys and values.
Scala has both mutable and immutable `Map` types, and this section demonstrates how to use the _immutable_ `Map`.

### Creating an immutable Map
Expand All @@ -433,13 +432,13 @@ val states = Map(
Once you have a `Map` you can traverse its elements in a `for` loop like this:

```scala
for ((k,v) <- states) println(s"key: $k, value: $v")
for (k, v) <- states do println(s"key: $k, value: $v")
```

The REPL shows how this works:

````
scala> for ((k,v) <- states) println(s"key: $k, value: $v")
scala> for (k, v) <- states do println(s"key: $k, value: $v")
key: AK, value: Alaska
key: AL, value: Alabama
key: AZ, value: Arizona
Expand All @@ -463,7 +462,7 @@ Add elements to an immutable map using `+` and `++`, remembering to assign the r
```scala
val a = Map(1 -> "one") // a: Map(1 -> one)
val b = a + (2 -> "two") // b: Map(1 -> one, 2 -> two)
val c = b + (
val c = b ++ Seq(
3 -> "three",
4 -> "four"
)
Expand All @@ -482,13 +481,13 @@ val a = Map(
4 -> "four"
)

a - 4 // Map(1 -> one, 2 -> two, 3 -> three)
a - 4 - 3 // Map(1 -> one, 2 -> two)
val b = a - 4 // b: Map(1 -> one, 2 -> two, 3 -> three)
val c = a - 4 - 3 // c: Map(1 -> one, 2 -> two)
```

### Updating Map elements

To update elements in an immutable map, use the `updated` method while assigning the result to a new variable:
To update elements in an immutable map, use the `updated` method (or the `+` operator) while assigning the result to a new variable:

```scala
val a = Map(
Expand All @@ -497,7 +496,8 @@ val a = Map(
3 -> "three"
)

val b = a.updated(3, "THREE!") // Map(1 -> one, 2 -> two, 3 -> THREE!)
val b = a.updated(3, "THREE!") // b: Map(1 -> one, 2 -> two, 3 -> THREE!)
val c = a + (2 -> "TWO...") // c: Map(1 -> one, 2 -> TWO..., 3 -> three)
```

### Traversing a Map
Expand All @@ -511,7 +511,7 @@ val states = Map(
"AZ" -> "Arizona"
)

for ((k,v) <- states) println(s"key: $k, value: $v")
for (k, v) <- states do println(s"key: $k, value: $v")
```

That being said, there are _many_ ways to work with the keys and values in a map.
Expand Down Expand Up @@ -558,6 +558,8 @@ val c = b ++ Seq(4, 1, 5, 5) // HashSet(5, 1, 2, 3, 4)

Notice that when you attempt to add duplicate elements, they’re quietly dropped.

Also notice that the order of iteration of the elements is arbitrary.


### Deleting elements from a Set

Expand Down
16 changes: 8 additions & 8 deletions _overviews/scala3-book/collections-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The following methods work on all of the sequence types, including `List`, `Vect
## Examples of common methods

To give you an overview of what you’ll see in the following sections, these examples show some of the most commonly used collections methods.
First, here are some methods don’t use lambdas:
First, here are some methods that don’t use lambdas:

```scala
val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10)
Expand Down Expand Up @@ -103,7 +103,7 @@ a.map(double(_))
a.map(double)
```

In the last example, when an anonymous function consists of one statement that takes a single argument, you don’t have to name the argument, so even `-` isn’t required.
In the last example, when an anonymous function consists of one function call that takes a single argument, you don’t have to name the argument, so even `_` isn’t required.

Finally, you can combine HOFs as desired to solve problems:

Expand Down Expand Up @@ -217,19 +217,19 @@ david
## `head`

The `head` method comes from Lisp and other earlier functional programming languages.
It’s used to print the first element (the head element) of a list:
It’s used to access the first element (the head element) of a list:

```scala
oneToTen.head // Int = 1
oneToTen.head // 1
names.head // adam
```

Because a `String` can be seen as a sequence of characters, you can also treat it like a list.
This is how `head` works on these strings:

```scala
"foo".head // Char = 'f'
"bar".head // Char = 'b'
"foo".head // 'f'
"bar".head // 'b'
```

`head` is a great method to work with, but as a word of caution it can also throw an exception when called on an empty collection:
Expand All @@ -242,7 +242,7 @@ emptyList.head // java.util.NoSuchElementException: head of empty
Because of this you may want to use `headOption` instead of `head`, especially when programming in a functional style:

```scala
emptyList.headOption // Option[Int] = None
emptyList.headOption // None
```

As shown, it doesn’t throw an exception, it simply returns the type `Option` that has the value `None`.
Expand All @@ -256,7 +256,7 @@ The `tail` method also comes from Lisp, and it’s used to print every element i
A few examples demonstrate this:

```scala
oneToTen.head // Int = 1
oneToTen.head // 1
oneToTen.tail // List(2, 3, 4, 5, 6, 7, 8, 9, 10)

names.head // adam
Expand Down
18 changes: 10 additions & 8 deletions _overviews/scala3-book/fp-functional-error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ makeInt(x) match
case None => println("That didn’t work.")
```

In this example, if `x` can be converted to an `Int`, the first `case` statement is executed; if `x` can’t be converted to an `Int`, the second `case` statement is executed.
In this example, if `x` can be converted to an `Int`, the expression on the right-hand side of the first `case` clause is evaluated; if `x` can’t be converted to an `Int`, the expression on the right-hand side of the second `case` clause is evaluated.



Expand Down Expand Up @@ -240,19 +240,20 @@ makeInt(x) match
Getting back to `null` values, a place where a `null` value can silently creep into your code is with a class like this:

```scala
class Address:
class Address(
var street1: String,
var street2: String,
var city: String,
var state: String,
var city: String,
var state: String,
var zip: String
)
```

While every address on Earth has a `street1` value, the `street2` value is optional.
As a result, the `street2` field can be assigned a `null` value:

```scala
val santa = new Address(
val santa = Address(
"1 Main Street",
null, // <-- D’oh! A null value!
"North Pole",
Expand All @@ -265,18 +266,19 @@ Historically, developers have used blank strings and null values in this situati
In Scala---and other modern languages---the correct solution is to declare up front that `street2` is optional:

```scala
class Address:
class Address(
var street1: String,
var street2: Option[String], // an optional value
var city: String,
var state: String,
var zip: String
)
```

Now developers can write more accurate code like this:

```scala
val santa = new Address(
val santa = Address(
"1 Main Street",
None, // 'street2' has no value
"North Pole",
Expand All @@ -288,7 +290,7 @@ val santa = new Address(
or this:

```scala
val santa = new Address(
val santa = Address(
"123 Main Street",
Some("Apt. 2B"),
"Talkeetna",
Expand Down
2 changes: 1 addition & 1 deletion _overviews/scala3-book/types-inferred.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ As with other statically typed programming languages, in Scala you can _declare_

```scala
val x: Int = 1
val x: Double = 1
val y: Double = 1
```

In those examples the types are _explicitly_ declared to be `Int` and `Double`, respectively.
Expand Down
4 changes: 2 additions & 2 deletions _overviews/scala3-book/types-type-classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ In Scala 3, _type classes_ are just _traits_ with one or more type parameters, l
trait Show[A]:
def show(a: A): String
```
Instances of `Show` for a particular type `A` witness that `A` we can show an instance of type `A`.
Instances of `Show` for a particular type `A` witness that we can show (i.e., produce a text representation of) an instance of type `A`.
For example, let’s look at the following `Show` instance for `Int` values:

```scala
Expand All @@ -43,7 +43,7 @@ toHtml(42)(ShowInt())
// results in "<p>The number is 42!</p>"
```

#### Automatically passing Type Class Instances
#### Automatically passing type class instances
Since type classes are a very important way to structure software, Scala 3 offers additional features that make working with them very convenient.
We discuss these additional features (which fall into the category of *Contextual Abstractions*) in a [later chapter][typeclasses-chapter] of this book.

Expand Down
16 changes: 8 additions & 8 deletions _overviews/scala3-book/types-variance.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ Let us also assume the following parameterized types:
trait Pipeline[T]:
def process(t: T): T

// an example of an covariant type
// an example of a covariant type
trait Producer[+T]:
def make: T

// an example of an contravariant type
// an example of a contravariant type
trait Consumer[-T]:
def take(t: T): Unit
```
Expand Down Expand Up @@ -73,7 +73,7 @@ In contrast to `Pipeline`, which is invariant, the type `Producer` is marked as
This is valid, since the type parameter is only used in a _return position_.

Marking it as covariant means that we can pass (or return) a `Producer[Book]` where a `Producer[Buyable]` is expected.
And in fact, this is sound: The type of `Producer[Buyable].make` only promises to _return_ a `Buyable`.
And in fact, this is sound. The type of `Producer[Buyable].make` only promises to _return_ a `Buyable`.
As a caller of `make`, we will be happy to also accept a `Book`, which is a subtype of `Buyable`---that is, it is _at least_ a `Buyable`.

This is illustrated by the following example, where the function `makeTwo` expects a `Producer[Buyable]`:
Expand Down Expand Up @@ -108,12 +108,12 @@ They have an additional ISBN method in our example, but you are free to ignore t
In contrast to the type `Producer`, which is marked as covariant, the type `Consumer` is marked as **contravariant** by prefixing the type parameter with a `-`.
This is valid, since the type parameter is only used in an _argument position_.

Marking it as contravariant means that we can pass (or return) a `Producer[Item]` where a `Producer[Buyable]` is expected.
That is, we have the subtyping relationship `Producer[Item] <: Producer[Buyable]`.
Remember, for type `Consumer`, it was the other way around, and we had `Consumer[Buyable] <: Consumer[Item]`.
Marking it as contravariant means that we can pass (or return) a `Consumer[Item]` where a `Consumer[Buyable]` is expected.
That is, we have the subtyping relationship `Consumer[Item] <: Consumer[Buyable]`.
Remember, for type `Producer`, it was the other way around, and we had `Producer[Buyable] <: Producer[Item]`.

And in fact, this is sound: The type of `Producer[Buyable].make` only promises us to _return_ a `Buyable`.
As a caller of `make`, we will be happy to also accept a `Book`, which is a subtype of `Buyable`---that is, it is _at least_ a `Buyable`.
And in fact, this is sound. The method `Consumer[Item].take` accepts an `Item`.
As a caller of `take`, we can also supply a `Buyable`, which will be happily accepted by the `Consumer[Item]` since `Buyable` is a subtype of `Item`---that is, it is _at least_ an `Item`.


#### Contravariant Types for Consumers
Expand Down