Skip to content

Commit f694314

Browse files
committed
Suggestions in the Scala 3 book (part 4)
I’ve reviewed the following chapters: - Scala Collections - Functional Programming - Types and the Type System - Contextual Abstractions
1 parent eff6c12 commit f694314

9 files changed

+57
-50
lines changed

Diff for: _overviews/scala3-book/ca-context-bounds.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ next-page: ca-given-imports
1313
- TODO: define "synthesized" and "synthesized arguments"
1414
{% endcomment %}
1515

16-
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.
16+
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.
1717
In that case you don’t have to define a parameter name, and can just provide the parameter type.
1818

1919

Diff for: _overviews/scala3-book/ca-given-using-clauses.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ The Scala compiler thus performs **term inference**.
4747
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`.
4848
So the program is equivalent to the one above.
4949

50-
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:
50+
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:
5151

5252
```scala
5353
// no need to come up with a parameter name

Diff for: _overviews/scala3-book/ca-implicit-conversions.md

+7-4
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ next-page: ca-summary
88
---
99

1010

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

1414
```scala
1515
given Conversion[String, Int] with
@@ -34,10 +34,13 @@ def plus1(i: Int) = i + 1
3434
plus1("1")
3535
```
3636

37+
> Note the clause `import scala.language.implicitConversions` at the beginning,
38+
> to enable implicit conversions in the file.
39+
3740
## Discussion
3841

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

4245
```scala
4346
given int2Integer: Conversion[Int, java.lang.Integer] =

Diff for: _overviews/scala3-book/collections-classes.md

+19-17
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ When you need more flexibility, see these pages at the end of this section for m
2828

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

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

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

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

111111
> `List` and `Vector` are often used when writing code in a functional style.
112-
> `ArrayBuffer` is commonly used when writing code in a mutable style.
112+
> `ArrayBuffer` is commonly used when writing code in an imperative style.
113113
> `ListBuffer` is used when you’re mixing styles, such as building a list.
114114
115115
The next several sections briefly demonstrate the `List`, `Vector`, and `ArrayBuffer` types.
@@ -291,7 +291,7 @@ val nums = Vector(1, 2, 3, 4, 5)
291291

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

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

385385
```scala
386-
var nums = ArrayBuffer(1, 2, 3) // ArrayBuffer(1, 2, 3)
386+
val nums = ArrayBuffer(1, 2, 3) // ArrayBuffer(1, 2, 3)
387387
nums += 4 // ArrayBuffer(1, 2, 3, 4)
388-
nums += (5, 6) // ArrayBuffer(1, 2, 3, 4, 5, 6)
389-
nums ++= List(7, 8) // ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8)
388+
nums ++= List(5, 6) // ArrayBuffer(1, 2, 3, 4, 5, 6)
390389
```
391390

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

416415
## Maps
417416

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

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

435434
```scala
436-
for ((k,v) <- states) println(s"key: $k, value: $v")
435+
for (k, v) <- states do println(s"key: $k, value: $v")
437436
```
438437

439438
The REPL shows how this works:
440439

441440
````
442-
scala> for ((k,v) <- states) println(s"key: $k, value: $v")
441+
scala> for (k, v) <- states do println(s"key: $k, value: $v")
443442
key: AK, value: Alaska
444443
key: AL, value: Alabama
445444
key: AZ, value: Arizona
@@ -463,7 +462,7 @@ Add elements to an immutable map using `+` and `++`, remembering to assign the r
463462
```scala
464463
val a = Map(1 -> "one") // a: Map(1 -> one)
465464
val b = a + (2 -> "two") // b: Map(1 -> one, 2 -> two)
466-
val c = b + (
465+
val c = b ++ Seq(
467466
3 -> "three",
468467
4 -> "four"
469468
)
@@ -482,13 +481,13 @@ val a = Map(
482481
4 -> "four"
483482
)
484483

485-
a - 4 // Map(1 -> one, 2 -> two, 3 -> three)
486-
a - 4 - 3 // Map(1 -> one, 2 -> two)
484+
val b = a - 4 // b: Map(1 -> one, 2 -> two, 3 -> three)
485+
val c = a - 4 - 3 // c: Map(1 -> one, 2 -> two)
487486
```
488487

489488
### Updating Map elements
490489

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

493492
```scala
494493
val a = Map(
@@ -497,7 +496,8 @@ val a = Map(
497496
3 -> "three"
498497
)
499498

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

503503
### Traversing a Map
@@ -511,7 +511,7 @@ val states = Map(
511511
"AZ" -> "Arizona"
512512
)
513513

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

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

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

561+
Also notice that the order of iteration of the elements is arbitrary.
562+
561563

562564
### Deleting elements from a Set
563565

Diff for: _overviews/scala3-book/collections-methods.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ The following methods work on all of the sequence types, including `List`, `Vect
3434
## Examples of common methods
3535

3636
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.
37-
First, here are some methods don’t use lambdas:
37+
First, here are some methods that don’t use lambdas:
3838

3939
```scala
4040
val a = List(10, 20, 30, 40, 10) // List(10, 20, 30, 40, 10)
@@ -103,7 +103,7 @@ a.map(double(_))
103103
a.map(double)
104104
```
105105

106-
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.
106+
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.
107107

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

@@ -217,19 +217,19 @@ david
217217
## `head`
218218

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

222222
```scala
223-
oneToTen.head // Int = 1
223+
oneToTen.head // 1
224224
names.head // adam
225225
```
226226

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

230230
```scala
231-
"foo".head // Char = 'f'
232-
"bar".head // Char = 'b'
231+
"foo".head // 'f'
232+
"bar".head // 'b'
233233
```
234234

235235
`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:
@@ -242,7 +242,7 @@ emptyList.head // java.util.NoSuchElementException: head of empty
242242
Because of this you may want to use `headOption` instead of `head`, especially when programming in a functional style:
243243

244244
```scala
245-
emptyList.headOption // Option[Int] = None
245+
emptyList.headOption // None
246246
```
247247

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

258258
```scala
259-
oneToTen.head // Int = 1
259+
oneToTen.head // 1
260260
oneToTen.tail // List(2, 3, 4, 5, 6, 7, 8, 9, 10)
261261

262262
names.head // adam

Diff for: _overviews/scala3-book/fp-functional-error-handling.md

+10-8
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ makeInt(x) match
106106
case None => println("That didn’t work.")
107107
```
108108

109-
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.
109+
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.
110110

111111

112112

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

242242
```scala
243-
class Address:
243+
class Address(
244244
var street1: String,
245245
var street2: String,
246-
var city: String,
247-
var state: String,
246+
var city: String,
247+
var state: String,
248248
var zip: String
249+
)
249250
```
250251

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

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

267268
```scala
268-
class Address:
269+
class Address(
269270
var street1: String,
270271
var street2: Option[String], // an optional value
271272
var city: String,
272273
var state: String,
273274
var zip: String
275+
)
274276
```
275277

276278
Now developers can write more accurate code like this:
277279

278280
```scala
279-
val santa = new Address(
281+
val santa = Address(
280282
"1 Main Street",
281283
None, // 'street2' has no value
282284
"North Pole",
@@ -288,7 +290,7 @@ val santa = new Address(
288290
or this:
289291

290292
```scala
291-
val santa = new Address(
293+
val santa = Address(
292294
"123 Main Street",
293295
Some("Apt. 2B"),
294296
"Talkeetna",

Diff for: _overviews/scala3-book/types-inferred.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ As with other statically typed programming languages, in Scala you can _declare_
1212

1313
```scala
1414
val x: Int = 1
15-
val x: Double = 1
15+
val y: Double = 1
1616
```
1717

1818
In those examples the types are _explicitly_ declared to be `Int` and `Double`, respectively.

Diff for: _overviews/scala3-book/types-type-classes.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ In Scala 3, _type classes_ are just _traits_ with one or more type parameters, l
2323
trait Show[A]:
2424
def show(a: A): String
2525
```
26-
Instances of `Show` for a particular type `A` witness that `A` we can show an instance of type `A`.
26+
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`.
2727
For example, let’s look at the following `Show` instance for `Int` values:
2828

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

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

Diff for: _overviews/scala3-book/types-variance.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ Let us also assume the following parameterized types:
2222
trait Pipeline[T]:
2323
def process(t: T): T
2424

25-
// an example of an covariant type
25+
// an example of a covariant type
2626
trait Producer[+T]:
2727
def make: T
2828

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

7575
Marking it as covariant means that we can pass (or return) a `Producer[Book]` where a `Producer[Buyable]` is expected.
76-
And in fact, this is sound: The type of `Producer[Buyable].make` only promises to _return_ a `Buyable`.
76+
And in fact, this is sound. The type of `Producer[Buyable].make` only promises to _return_ a `Buyable`.
7777
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`.
7878

7979
This is illustrated by the following example, where the function `makeTwo` expects a `Producer[Buyable]`:
@@ -108,12 +108,12 @@ They have an additional ISBN method in our example, but you are free to ignore t
108108
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 `-`.
109109
This is valid, since the type parameter is only used in an _argument position_.
110110

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

115-
And in fact, this is sound: The type of `Producer[Buyable].make` only promises us to _return_ a `Buyable`.
116-
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`.
115+
And in fact, this is sound. The method `Consumer[Item].take` accepts an `Item`.
116+
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`.
117117

118118

119119
#### Contravariant Types for Consumers

0 commit comments

Comments
 (0)