Skip to content

Small edits while reviewing a comment by Vinz #1870

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 2 commits into from
Jan 6, 2021
Merged
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
55 changes: 34 additions & 21 deletions _overviews/scala3-book/domain-modeling-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ next-page: domain-modeling-oop
---


Scala 3 provides us with many different tools to model the world around us:
Scala 3 provides many different constructs so we can model the world around us:

- Classes
- Objects
Expand All @@ -32,8 +32,9 @@ class Person(var name: String, var vocation: String)
class Book(var title: String, var author: String, var year: Int)
class Movie(var name: String, var director: String, var year: Int)
```
The examples above show that Scala has a very lightweight way to declare classes.
The definition of the class `Person` roughly corresponds to the following, more explicit, version

These examples show that Scala has a very lightweight way to declare classes.
The definition of the class `Person` roughly corresponds to the following, more explicit, version:

```scala
class Person:
Expand All @@ -49,7 +50,8 @@ class Person:
name = _name
vocation = _vocation
```
defining the two fields `name` and `vocation` together with a constructor that accepts values for the two fields and assigns them.

This version defines the two fields `name` and `vocation`, together with a constructor that accepts values for those two fields and assigns them.

All of the parameters of our example classes are defined as `var` fields, which means they are mutable: you can read them, and also modify them.
If you want them to be immutable---read only---create them as `val` fields instead.
Expand All @@ -67,7 +69,7 @@ However, with [creator applications][creator] this isn’t required in Scala 3:
val p = Person("Robert Allen Zimmerman", "Harmonica Player")
```

Once you have an instance of a class, you can access its fields, which in this example are all constructor parameters:
Once you have an instance of a class such as `p`, you can access its fields, which in this example are all constructor parameters:

```scala
p.name // "Robert Allen Zimmerman"
Expand Down Expand Up @@ -227,7 +229,7 @@ This is shown in the previous `Socket` example.

An object is a class that has exactly one instance.
It’s initialized lazily when its members are referenced, similar to a `lazy val`.
Objects in Scala allow grouping methods and fields under one namespace, similar to how can use `static` members on a class in Java, Javascript (ES6) or `@staticmethod` in Python.
Objects in Scala allow grouping methods and fields under one namespace, similar to how you use `static` members on a class in Java, Javascript (ES6), or `@staticmethod` in Python.

Declaring an `object` is similar to declaring a `class`.
Here’s an example of a “string utilities” object that contains a set of methods for working with strings:
Expand All @@ -245,11 +247,22 @@ We can use the object as follows:
StringUtil.truncate("Chuck Bartowski", 5) // "Chuck"
```

Importing in Scala is very flexible and allows us to import all members of an object:
Importing in Scala is very flexible, and allows us to import _all_ members of an object:

```scala
import StringUtils._
truncate("Chuck Bartowski", 5) // "Chuck"
truncate("Chuck Bartowski", 5) // "Chuck"
containsWhitespace("Sarah Walker") // true
isNullOrEmpty("John Casey") // false
```

or just _some_ members:

```scala
import StringUtils.{truncate, containsWhitespace}
truncate("Charles Carmichael", 7) // "Charles"
containsWhitespace("Captain Awesome") // true
isNullOrEmpty("Morgan Grimes") // Not found: isNullOrEmpty (error)
```

Objects can also contain fields, which are also accessed like static members:
Expand All @@ -266,7 +279,7 @@ println(MathConstants.PI) // 3.14159

## Companion objects

An `object` that has the same name as a class, and is declared in the same file as the class, is called a _"companion object"_.
An `object` that has the same name as a class, and is declared in the same file as the class, is called a _"companion object_."
Similarly, the corresponding class is called the object’s companion class.
A companion class or object can access the private members of its companion.

Expand All @@ -287,7 +300,7 @@ circle1.area
```

In this example the `area` method that’s available to each instance uses the `calculateArea` method that’s defined in the companion object.
If you’re familiar with Java, `calculateArea` is similar to a static method.
Once again, `calculateArea` is similar to a static method in Java.
Also, because `calculateArea` is private, it can’t be accessed by other code, but as shown, it can be seen by instances of the `Circle` class.

### Other uses
Expand All @@ -300,7 +313,7 @@ Companion objects can be used for several purposes:
- They can contain `apply` methods, which---thanks to some syntactic sugar---work as factory methods to construct new instances
- They can contain `unapply` methods, which are used to deconstruct objects, such as with pattern matching

Here’s a quick look at how `apply` methods that can be used as factory methods to create new objects:
Here’s a quick look at how `apply` methods can be used as factory methods to create new objects:

```scala
class Person:
Expand Down Expand Up @@ -436,8 +449,8 @@ class Dog(name: String, var age: Int) extends Pet(name):

val d = Dog("Fido", 1)
```
Traits are more flexible to compose (you can mix in multiple traits, but only extend one class) and should most of the time be preferred to classes.
The rule of thumb is to use classes whenever you want to create instances of a particular type and traits when you want to decompose and reuse behaviour.
Traits are more flexible to compose---you can mix in multiple traits, but only extend one class---and should be preferred to classes and abstract classes most of the time.
The rule of thumb is to use classes whenever you want to create instances of a particular type, and traits when you want to decompose and reuse behaviour.


## Enums
Expand Down Expand Up @@ -466,7 +479,7 @@ import CrustSize._
val currentCrustSize = Small
```

Enum values can be compared using equals (`==`) and matched on:
Enum values can be compared using equals (`==`), and also matched on:

```scala
// if/then
Expand Down Expand Up @@ -529,11 +542,11 @@ The section on [algebraic datatypes][adts] and the [reference documentation][ref
## Case classes

Case classes are used to model immutable data structures.
Take the following example,
Take the following example:
```scala
case class Person(name: String, relation: String)
```
Since we declared `Person` as a case class, the fields `name` and `relation` are public and immutable by default.
Since we declare `Person` as a case class, the fields `name` and `relation` are public and immutable by default.
We can create instances of case classes as follows:
```scala
val christina = Person("Christina", "niece")
Expand All @@ -544,9 +557,9 @@ christina.name = "Fred" // error: reassignment to val
```
Since the fields of a case class are assumed to be immutable, the Scala compiler can generate many helpful methods for you:
* An `unapply` method is generated, which allows you to perform pattern matching on a case class (that is, `case Person(n, r) => ...`).
* A `copy` method is generated in the class, which is very useful to create modified copies of an instance
* A `copy` method is generated in the class, which is very useful to create modified copies of an instance.
* `equals` and `hashCode` methods using structural equality are generated, allowing you to use instances of case classes in `Map`s.
* A default `toString` method is generated, which is helpful for debugging
* A default `toString` method is generated, which is helpful for debugging.

These additional features are demonstrated in the below example:
```scala
Expand Down Expand Up @@ -576,8 +589,8 @@ As mentioned, case classes support functional programming (FP):

- In FP you try to avoid mutating data structures.
It thus makes sense that constructor fields default to `val`.
Since instances of case classes are not changed, they can easily be shared without fearing mutation or race conditions.
- Instead of mutating one instance, you can use the `copy` method as a template to create a new (potentially changed) instance.
Since instances of case classes can’t be changed, they can easily be shared without fearing mutation or race conditions.
- Instead of mutating an instance, you can use the `copy` method as a template to create a new (potentially changed) instance.
This process can be referred to as “update as you copy.”
- Having an `unapply` method auto-generated for you also lets case classes be used in advanced ways with pattern matching.

Expand Down Expand Up @@ -621,7 +634,7 @@ case Teacher(name, whatTheyTeach) =>
```

Those patterns work because `Student` and `Teacher` are defined as case classes that have `unapply` methods whose type signature conforms to a certain standard.
Technically, the specific type of pattern matching shown in these examples is known as a *constructor pattern*.
Technically, the specific type of pattern matching shown in these examples is known as a _constructor pattern_.

> The Scala standard is that an `unapply` method returns the case class constructor fields in a tuple that’s wrapped in an `Option`.
> The “tuple” part of the solution was shown in the previous lesson.
Expand Down