diff --git a/Classes Vs Case Classes/Case Classes Encoding/task.md b/Classes Vs Case Classes/Case Classes Encoding/task.md index 12afb60..85ec115 100644 --- a/Classes Vs Case Classes/Case Classes Encoding/task.md +++ b/Classes Vs Case Classes/Case Classes Encoding/task.md @@ -11,11 +11,11 @@ because it is very common in practice. So, when we define a case class, the Scala compiler defines a class enhanced with some more methods and a companion object. -For instance, the following case class definition: +For instance, look at the following case class definition: case class Note(name: String, duration: String, octave: Int) -Expands to the following class definition: +It expands to the following class definition: class Note(_name: String, _duration: String, _octave: Int) extends Serializable { diff --git a/Classes Vs Case Classes/Creation and Manipulation/task.md b/Classes Vs Case Classes/Creation and Manipulation/task.md index 44b4658..e80d8aa 100644 --- a/Classes Vs Case Classes/Creation and Manipulation/task.md +++ b/Classes Vs Case Classes/Creation and Manipulation/task.md @@ -1,10 +1,10 @@ -In the previous sections we have seen how case classes could be -used to achieve information aggregation, and also how classes +In the previous sections, we have seen how case classes could be +used to achieve information aggregation and also how classes could be used to achieve data abstraction or to define stateful objects. -What are the relationship between classes and case classes? How +What is the relationship between classes and case classes? How do they differ? ## Creation and Manipulation diff --git a/Classes Vs Case Classes/Equality/task.md b/Classes Vs Case Classes/Equality/task.md index 73056ef..b4c0bbc 100644 --- a/Classes Vs Case Classes/Equality/task.md +++ b/Classes Vs Case Classes/Equality/task.md @@ -14,7 +14,7 @@ values, whereas the same definitions of notes lead to equal values. As we have seen in the previous sections, stateful classes introduce a notion of *identity* that does not exist in case classes. Indeed, the value of `BankAccount` can change over -time whereas the value of a `Note` is immutable. +time, whereas the value of `Note` is immutable. In Scala, by default, comparing objects will compare their identity, but in the case of case class instances, the equality is redefined to compare the values of @@ -38,5 +38,5 @@ implement their equality). ## Exercise -- Complete the creation of two instances of the `BankAccount`. -- Complete the creation of two instances of a `c3` `Note`. +- Complete the creation of two instances of `BankAccount`. +- Complete the creation of two instances of `c3` `Note`. diff --git a/Definitions and Evaluation/Multiple Parameters/task.md b/Definitions and Evaluation/Multiple Parameters/task.md index 1313742..c85dafd 100644 --- a/Definitions and Evaluation/Multiple Parameters/task.md +++ b/Definitions and Evaluation/Multiple Parameters/task.md @@ -7,7 +7,7 @@ Separate several parameters with commas: ## Parameters and Return Types -Function parameters come with their type, which is given after a colon +Function parameters come with their type, which is given after a colon: def power(x: Double, y: Int): Double = ... @@ -15,9 +15,9 @@ If a return type is given, it follows the parameter list. ## Val vs Def -The right hand side of a `def` definition is evaluated on each use. +The right-hand side of a `def` definition is evaluated on each use. -The right hand side of a `val` definition is evaluated at the point of the definition +The right-hand side of a `val` definition is evaluated at the point of the definition itself. Afterwards, the name refers to the value. val x = 2 @@ -27,11 +27,11 @@ For instance, `y` above refers to `4`, not `square(2)`. ## Evaluation of Function Applications -Applications of parametrized functions are evaluated in a similar way as +Applications of parametrized functions are evaluated in a way similar to operators: 1. Evaluate all function arguments, from left to right. - 2. Replace the function application by the function's right-hand side, and, at the same time + 2. Replace the function application by the function's right-hand side and, at the same time 3. Replace the formal parameters of the function by the actual arguments. ## Example @@ -52,7 +52,7 @@ This scheme of expression evaluation is called the *substitution model*. The idea underlying this model is that all evaluation does is *reduce an expression to a value*. -It can be applied to all expressions, as long as they have no side effects. +It can be applied to all expressions as long as they have no side effects. The substitution model is formalized in the λ-calculus, which gives a foundation for functional programming. @@ -69,8 +69,8 @@ No. Here is a counter-example: ## Value Definitions and Termination -The difference between `val` and `def` becomes apparent when the right -hand side does not terminate. Given +The difference between `val` and `def` becomes apparent when the right-hand +side does not terminate. Given def loop: Int = loop @@ -124,5 +124,5 @@ Scala normally uses call-by-value. ## Exercise Complete the following definition of the `triangleArea` function, -which takes a triangle base and height as parameters and returns +which takes the base and height of a triangle as parameters and returns its area. diff --git a/Definitions and Evaluation/Naming Things/task.md b/Definitions and Evaluation/Naming Things/task.md index 6e73b68..00e2e2a 100644 --- a/Definitions and Evaluation/Naming Things/task.md +++ b/Definitions and Evaluation/Naming Things/task.md @@ -1,12 +1,12 @@ ## Naming Things -Consider the following program that computes the area of a disc +Consider the following program, which computes the area of a disc whose radius is `10`: 3.14159 * 10 * 10 -To make complex expressions more readable we can give meaningful names to +To make complex expressions more readable, we can give meaningful names to intermediate expressions: val radius = 10 @@ -14,12 +14,12 @@ intermediate expressions: pi * radius * radius -Besides making the last expression more readable it also allows us to +Besides making the last expression more readable, it also allows us to not repeat the actual value of the radius. ## Evaluation -A name is evaluated by replacing it with the right hand side of its definition +A name is evaluated by replacing it with the right-hand side of its definition. ### Example diff --git a/Functional Loops/Computing the Square Root of a Value/task.md b/Functional Loops/Computing the Square Root of a Value/task.md index c35177f..742160d 100644 --- a/Functional Loops/Computing the Square Root of a Value/task.md +++ b/Functional Loops/Computing the Square Root of a Value/task.md @@ -1,7 +1,7 @@ ## Computing the Square Root of a Value -We will define in this section a method +In this section, we will define a square root calculation method: /** Calculates the square root of parameter x */ def sqrt(x: Double): Double = ... @@ -38,7 +38,7 @@ Recursive methods need an explicit return type in Scala. For non-recursive methods, the return type is optional. -Second, we define a method `improve` to improve an estimate and a test to check for termination: +Second, we define a method `improve` to improve the estimate and a test to check for termination: def improve(guess: Double, x: Double) = (guess + x / guess) / 2 diff --git a/Functional Loops/Conditional Expressions/task.md b/Functional Loops/Conditional Expressions/task.md index 8a1539a..b52aa81 100644 --- a/Functional Loops/Conditional Expressions/task.md +++ b/Functional Loops/Conditional Expressions/task.md @@ -1,28 +1,28 @@ ## Conditional Expressions -To express choosing between two alternatives, Scala -has a conditional expression `if-else`. +To express the choice between two alternatives, Scala +uses a conditional expression `if-else`. -It looks like a `if-else` in Java, but is used for expressions, not statements. +It looks like the `if-else` in Java but is used for expressions, not statements. Example: ~~~ def abs(x: Double) = if (x >= 0) x else -x ~~~ -`x >= 0` is a *predicate*, of type `Boolean`. +Here `x >= 0` is a *predicate* of type `Boolean`. ## Boolean Expressions -Boolean expressions `b` can be composed of +Boolean expressions `b` can include: true false // Constants !b // Negation b && b // Conjunction b || b // Disjunction -and of the usual comparison operations: - e <= e, e >= e, e < e, e > e, e == e, e != e +They can also contain the usual comparison operations: + e <= e, e >= e, e < e, e > e, e == e, e != e. ## Rewrite rules for Booleans @@ -41,11 +41,11 @@ We say these expressions use "short-circuit evaluation". ## Summary -You have seen simple elements of functional programing in Scala. +You have seen simple elements of functional programing in Scala: - - arithmetic and boolean expressions - - conditional expressions if-else - - functions with recursion + - arithmetic and boolean expressions; + - conditional expressions if-else; + - functions with recursion. You have learned the difference between the call-by-name and call-by-value evaluation strategies. @@ -55,4 +55,4 @@ the substitution model. ## Exercise -Complete the following method definition that computes the factorial of a number. +Complete the definition of the following method, which computes the factorial of a number. diff --git a/Higher Order Functions/Higher Order Functions/task.md b/Higher Order Functions/Higher Order Functions/task.md index b276e06..81004bf 100644 --- a/Higher Order Functions/Higher Order Functions/task.md +++ b/Higher Order Functions/Higher Order Functions/task.md @@ -8,19 +8,19 @@ can be passed as a parameter and returned as a result. This provides a flexible way to compose programs. -Functions that take other functions as parameters or that return functions -as results are called *higher order functions*. +Functions that take other functions as parameters or return functions +as a result are called *higher order functions*. ### Motivation Consider the following programs. -Take the sum of the integers between `a` and `b`: +Taking the sum of the integers between `a` and `b`: def sumInts(a: Int, b: Int): Int = if (a > b) 0 else a + sumInts(a + 1, b) -Take the sum of the cubes of all the integers between `a` +Taking the sum of the cubes of all the integers between `a` and `b`: def cube(x: Int): Int = x * x * x @@ -28,7 +28,7 @@ and `b`: def sumCubes(a: Int, b: Int): Int = if (a > b) 0 else cube(a) + sumCubes(a + 1, b) -Take the sum of the factorials of all the integers between `a` +Taking the sum of the factorials of all the integers between `a` and `b`: def sumFactorials(a: Int, b: Int): Int = @@ -72,22 +72,22 @@ Passing functions as parameters leads to the creation of many small functions. Sometimes it is tedious to have to define (and name) these functions using `def`. -Compare to strings: We do not need to define a string using `val`. Instead of: +Compare to strings: we do not need to define a string using `val`. Instead of: val str = "abc"; println(str) -We can directly write: +we can directly write: println("abc") -because strings exist as *literals*. Analogously we would like function +because strings exist as *literals*. Analogously, we can use function literals, which let us write a function without giving it a name. These are called *anonymous functions*. ### Anonymous Function Syntax -Example of a function that raises its argument to a cube: +Example of a function that raises its argument to the third power: (x: Int) => x * x * x @@ -108,7 +108,7 @@ can always be expressed using `def` as follows: { def f(x1: T1, …, xn: Tn) = e ; f } -where `f` is an arbitrary, fresh name (that's not yet used in the program). +where `f` is an arbitrary, fresh name (that has not yet been used in the program). One can therefore say that anonymous functions are *syntactic sugar*. diff --git a/Imperative Programming/Imperative Loops/task.md b/Imperative Programming/Imperative Loops/task.md index 5cb81eb..6fea628 100644 --- a/Imperative Programming/Imperative Loops/task.md +++ b/Imperative Programming/Imperative Loops/task.md @@ -3,7 +3,7 @@ In the first sections, we saw how to write loops using recursion. -### While-Loops +### While Loops We can also write loops with the `while` keyword: @@ -14,18 +14,18 @@ We can also write loops with the `while` keyword: r } -As long as the condition of a *while* statement is `true`, +As long as the condition of the `while` statement is `true`, its body is evaluated. -### For-Loops +### For Loops -In Scala there is a kind of `for` loop: +In Scala, there is a kind of `for` loop, too: for (i <- 1 until 3) { System.out.print(i.toString + " ") } This displays `1 2`. -For-loops translate similarly to for-expressions, but using the +`For` loops translate similarly to `for` expressions but use the `foreach` combinator instead of `map` and `flatMap`. `foreach` is defined on collections with elements of type `A` as follows: @@ -37,7 +37,7 @@ Example: for (i <- 1 until 3; j <- "abc") println(s"$i $j") -translates to: +It translates to: (1 until 3) foreach (i => "abc" foreach (j => println(s"$i $j"))) diff --git a/Imperative Programming/Stateful Objects/task.md b/Imperative Programming/Stateful Objects/task.md index b50471e..a98e288 100644 --- a/Imperative Programming/Stateful Objects/task.md +++ b/Imperative Programming/Stateful Objects/task.md @@ -15,7 +15,7 @@ Programs can be evaluated by *rewriting*: side, and, at the same time, by replacing the formal parameters by the actual arguments. -Say you have the following two functions `iterate` and `square`: +Say, you have the following two functions `iterate` and `square`: def iterate(n: Int, f: Int => Int, x: Int) = if (n == 0) x else iterate(n-1, f, f(x)) @@ -34,10 +34,10 @@ Then the call `iterate(1, square, 3)` gets rewritten as follows: Rewriting can be done anywhere in a term, and all rewritings which terminate lead to the same solution. -This is an important result of the λ-calculus, the theory +This is an important result of the λ-calculus, i.e., the theory behind functional programming. -For instance, these two rewriting will eventually lead to the same result: +For instance, these two rewritings will eventually lead to the same result: if (1 == 0) 3 else iterate(1 - 1, square, square(3)) iterate(0, square, square(3)) @@ -49,7 +49,7 @@ For instance, these two rewriting will eventually lead to the same result: ### Stateful Objects One normally describes the world as a set of objects, some of which -have state that *changes* over the course of time. +have a state that *changes* over the course of time. An object *has a state* if its behavior is influenced by its history. @@ -62,7 +62,7 @@ the account. Every form of mutable state is constructed from variables. -A variable definition is written like a value definition, but with the +A variable definition is written like a value definition but with the keyword `var` in place of `val`: var x: String = "abc" @@ -106,13 +106,13 @@ through assignments. Note that `balance` is `private` in the `BankAccount` class, it therefore cannot be accessed from outside the class. -To create bank accounts, we use the usual notation for object creation: +To create bank accounts, we use the common notation for object creation: val account = new BankAccount ## Working with Mutable Objects -Here is a program that manipulates bank accounts. +Here is a program that manipulates bank accounts: val account = new BankAccount // account: BankAccount = BankAccount account deposit 50 // @@ -126,9 +126,9 @@ Clearly, accounts are stateful objects. ## Identity and Change Assignment poses the new problem of deciding whether two expressions -are "the same" +are "the same". -When one excludes assignments and one writes: +When one excludes assignments and writes: val x = E; val y = E @@ -137,9 +137,9 @@ where `E` is an arbitrary expression, then it is reasonable to assume that val x = E; val y = x -(This property is usually called *referential transparency*) +(This property is usually called *referential transparency*.) -But once we allow the assignment, the two formulations are different. For example: +But once we allow assignment, the two formulations become different. For example: val x = new BankAccount val y = new BankAccount @@ -161,7 +161,7 @@ In a somewhat informal way, this property is stated as follows: ### Testing for Operational Equivalence -To test if `x` and `y` are the same, we must +To test if `x` and `y` are the same, we must: - Execute the definitions followed by an arbitrary sequence `S` of operations that involves `x` and `y`, observing the possible outcomes. @@ -221,11 +221,12 @@ the `x` in the definition of `y` could be replaced by `new BankAccount`. But we have seen that this change leads to a different program! -The substitution model ceases to be valid when we add the assignment. +The substitution model ceases to be valid when we add assignment. It is possible to adapt the substitution model by introducing a *store*, but this becomes considerably more complicated. ## Exercise -Complete the `deposit` definition to return the sum of `balance` and `amount` in case of the positive one. \ No newline at end of file +Complete the `deposit` definition to return the sum of `balance` and `amount` in case the `amount` is positive. +Complete the `withdraw` definition to return the difference between `balance` and `amount` in case the `amount` is negative. \ No newline at end of file diff --git a/Lazy Evaluation/Lazy Evaluation/task.md b/Lazy Evaluation/Lazy Evaluation/task.md index 867cc7e..78758ec 100644 --- a/Lazy Evaluation/Lazy Evaluation/task.md +++ b/Lazy Evaluation/Lazy Evaluation/task.md @@ -1,29 +1,29 @@ ## Lazy Evaluation -The proposed `LazyList` implementation suffers from a serious potential performance -problem: If `tail` is called several times, the corresponding stream +The proposed `LazyList` implementation poses a serious potential performance +problem: if `tail` is called several times, the corresponding stream will be recomputed each time. This problem can be avoided by storing the result of the first evaluation of `tail` and re-using the stored result instead of recomputing `tail`. -This optimization is sound, since in a purely functional language an +This optimization is sound, since in a purely functional language, an expression produces the same result each time it is evaluated. We call this scheme *lazy evaluation* (as opposed to *by-name evaluation* in -the case where everything is recomputed, and *strict evaluation* for normal -parameters and `val` definitions.) +the case where everything is recomputed and *strict evaluation* for normal +parameters and `val` definitions). ### Lazy Evaluation in Scala Haskell is a functional programming language that uses lazy evaluation by default. -Scala uses strict evaluation by default, but allows lazy evaluation of value definitions +Scala uses strict evaluation by default but allows lazy evaluation of value definitions with the `lazy val` form: lazy val x = expr ## Exercise -Complete the `y` variable declaration for it to be lazy. \ No newline at end of file +Complete the `y` variable declaration to make it lazy. \ No newline at end of file diff --git a/Lazy Evaluation/Lazy Lists/task.md b/Lazy Evaluation/Lazy Lists/task.md index a11415e..13552be 100644 --- a/Lazy Evaluation/Lazy Lists/task.md +++ b/Lazy Evaluation/Lazy Lists/task.md @@ -1,7 +1,7 @@ ## Motivation -Consider the following program that finds the second prime number between 1000 and 10000: +Consider the following program, which finds the second prime number between 1000 and 10000: ((1000 to 10000) filter isPrime)(1) @@ -16,19 +16,19 @@ This is *much* shorter than the recursive alternative: def secondPrime(from: Int, to: Int) = nthPrime(from, to, 2) -But from a standpoint of performance, the first version is pretty bad; it constructs +But from a standpoint of performance, the first version is pretty bad: it constructs *all* prime numbers between `1000` and `10000` in a list, but only ever looks at the first two elements of that list. -Reducing the upper bound would speed things up, but risks that we miss the +Reducing the upper bound would speed things up, but it creates a risks of missing the second prime number altogether. ## Delayed Evaluation -However, we can make the short-code efficient by using a trick: +However, we can make the short code efficient by using a trick: - Avoid computing the tail of a sequence until it is needed for the evaluation - result (which might be never) + result (which might be never). This idea is implemented in a new class, the `LazyList`. @@ -36,7 +36,7 @@ LazyLists are similar to lists, but their elements are evaluated only ''on deman ## Defining LazyLists -LazyLists are defined from a constructor `LazyList.cons`. +LazyLists are defined from the `LazyList.cons` constructor. For instance, @@ -51,16 +51,16 @@ between `lo` and `hi`: if (lo >= hi) LazyList.empty else LazyList.cons(lo, llRange(lo + 1, hi)) -Compare to the same function that produces a list: +Compare it to a similar function that produces a list: def listRange(lo: Int, hi: Int): List[Int] = if (lo >= hi) Nil else lo :: listRange(lo + 1, hi) -The functions have almost identical structure yet they evaluate quite differently. +The functions have almost identical structure, yet they evaluate quite differently. - `listRange(start, end)` will produce a list with `end - start` elements and return it. -- `llRange(start, end)` returns a single object of type `LazyList` with `start` as head element. +- `llRange(start, end)` returns a single object of type `LazyList` with `start` as the head element. - The other elements are only computed when they are needed, where “needed” means that someone calls `tail` on the stream. ## Methods on LazyLists @@ -75,15 +75,15 @@ The one major exception is `::`. `x :: xs` always produces a list, never a lazy list. -There is however an alternative operator `#::` which produces a lazy list. +There is, however, an alternative operator `#::`, which produces a lazy list: x #:: xs == LazyList.cons(x, xs) -`#::` can be used in expressions as well as patterns. +`#::` can be used in expressions as well as in patterns. ## Implementation of LazyLists -The implementation of lazy lists is quite close to the one of lists. +The implementation of lazy lists is quite close to that of lists. Here's the class `LazyList`: @@ -118,9 +118,9 @@ That's why the second argument to `LazyList.cons` is not evaluated at the point Instead, it will be evaluated each time someone calls `tail` on a `LazyList` object. -In Scala 2.13, LazyList (previously Stream) became fully lazy from head to tail. To make it possible, -methods (`filter`, `flatMap`...) are implemented in a way where the head is not being evaluated if is -not explicitly indicated. +In Scala 2.13, `LazyList` (previously `Stream`) became fully lazy from head to tail. To make it possible, +methods (`filter`, `flatMap`, etc.) are implemented in a way where the head is not evaluated unless it is +explicitly indicated. For instance, here's `filter`: @@ -146,6 +146,6 @@ For instance, here's `filter`: ## Exercise Consider the modification of `llRange` given in the code editor. When you write -`llRange(1, 10).take(3).toList` what is the value of `rec`? +`llRange(1, 10).take(3).toList`, what is the value of `rec`? -Be careful, head is evaluating too! +Be careful, the head is also evaluated! diff --git a/Lexical Scopes/Lexical Scoping/task.md b/Lexical Scopes/Lexical Scoping/task.md index e66a304..5b19312 100644 --- a/Lexical Scopes/Lexical Scoping/task.md +++ b/Lexical Scopes/Lexical Scoping/task.md @@ -1,8 +1,8 @@ ## Lexical Scoping -Definitions of outer blocks are visible inside a block unless they are shadowed. -Shadowed definitions are ones which are redefined in a lower scope. +The definitions of outer blocks are visible inside the block unless they are shadowed. +Shadowed definitions are those that are redefined in a lower scope. Therefore, we can simplify `sqrt` by eliminating redundant occurrences of the `x` parameter, which means the same thing everywhere: @@ -41,7 +41,7 @@ separated by semicolons: ### Semicolons and infix operators One issue with Scala's semicolon convention is how to write expressions that span -several lines. For instance: +several lines. For instance, these two lines: ``` someLongExpression ``` @@ -57,15 +57,15 @@ would be interpreted as *two* expressions: ``` There are two ways to overcome this problem. -You could write the multi-line expression in parentheses, because semicolons -are never inserted inside `(…)`: +You could write the multi-line expression in parentheses because semicolons +are never used inside `(…)`: ``` (someLongExpression ``` ``` + someOtherExpression) ``` -Or you could write the operator on the first line, because this tells the Scala +Or otherwise, you could write the operator on the first line because this tells the Scala compiler that the expression is not yet finished: ``` someLongExpression + @@ -76,7 +76,7 @@ compiler that the expression is not yet finished: ### Top-Level Definitions In real Scala programs, `def` and `val` definitions must be written -within a top-level *object definition*, in .scala file: +within a top-level *object definition*, in a .scala file: object MyExecutableProgram { val myVal = … @@ -84,7 +84,7 @@ within a top-level *object definition*, in .scala file: } The above code defines an *object* named `MyExecutableProgram`. You -can refer to its *members* using the usual dot notation: +can refer to its *members* using the common dot notation: MyExecutableProgram.myMethod @@ -94,7 +94,7 @@ is not nested within another definition. ### Packages and Imports Top-level definitions can be organized in *packages*. -To place a class or object inside a package, use a package clause +To place a class or an object inside a package, use a package clause at the top of your source file: // file foo/Bar.scala @@ -140,8 +140,8 @@ Some entities are automatically imported in any Scala program. These are: - - All members of package `scala` - - All members of package `java.lang` + - All members of the `scala` package; + - All members of the `java.lang` package; - All members of the singleton object `scala.Predef`. Here are the fully qualified names of some types and functions @@ -172,9 +172,9 @@ For instance, here is the "Hello World!" program in Scala: def main(args: Array[String]) = println("hello world!") } -Once this program is compiled, you can start it from the command line with +Once this program is compiled, you can start it from the command line with the following command: $ scala Hello ## Exercise -Complete the expression for `y` in the `Baz` object for it to represent the sum of `x` fields from the objects `Foo` and `Bar`. \ No newline at end of file +Complete the expression for `y` in the `Baz` object to make it represent the sum of `x` fields from the objects `Foo` and `Bar`. \ No newline at end of file diff --git a/Lexical Scopes/Nested Functions/task.md b/Lexical Scopes/Nested Functions/task.md index 81c8f0c..e8e485a 100644 --- a/Lexical Scopes/Nested Functions/task.md +++ b/Lexical Scopes/Nested Functions/task.md @@ -1,7 +1,7 @@ ## Nested functions -It's good functional programming style to split up a task into many small functions. +It's good functional programming style to split up a task into several small functions. But the names of functions like `sqrtIter`, `improve`, and `isGoodEnough` (defined in the previous section) matter only for the *implementation* of `sqrt`, not for its *usage*. @@ -39,12 +39,12 @@ putting the auxiliary functions inside `sqrt`. It contains a sequence of definitions or expressions.\ The last element of a block is an expression that defines its value.\ This return expression can be preceded by auxiliary definitions.\ - Blocks are themselves expressions; a block may appear everywhere an expression can. + Blocks are expressions themselves; a block may appear anywhere an expression can. ### Blocks and Visibility The definitions inside a block are only visible from within the block.\ - The definitions inside a block *shadow* definitions of the same names + The definitions inside a block *shadow* the definitions of the same names outside the block. ## Exercise: Scope Rules diff --git a/Navigating Around/Editor/task.md b/Navigating Around/Editor/task.md index 2a64362..1233b8d 100644 --- a/Navigating Around/Editor/task.md +++ b/Navigating Around/Editor/task.md @@ -1,6 +1,6 @@ ## Editor -The Editor is your playground where you will be programming. You can experiment here while you work on the theoretical tasks and quizzes without being checked. +The Editor is your playground where you will be programming. While working on the theoretical tasks and quizzes, you can experiment here without being checked. For programming assignments, the Editor is where you’ll fix the existing code or write your own code from scratch. This code will be checked. diff --git a/Navigating Around/First Steps/task.md b/Navigating Around/First Steps/task.md index 457bd9e..53640e8 100644 --- a/Navigating Around/First Steps/task.md +++ b/Navigating Around/First Steps/task.md @@ -2,7 +2,7 @@ This lesson will help you take your first steps with the [EduTools plugin](https://www.jetbrains.com/help/education/educational-products.html) and use it to learn Scala. -With the EduTools plugin, you can learn programming languages and tools by completing coding tasks, and get instant feedback right inside the IDE. +With the EduTools plugin, you can learn programming languages and tools by completing coding tasks and get instant feedback right inside the IDE. Enough talking – let's get started! diff --git a/Navigating Around/Task Description/task.md b/Navigating Around/Task Description/task.md index 95a90cd..a4226da 100644 --- a/Navigating Around/Task Description/task.md +++ b/Navigating Around/Task Description/task.md @@ -11,10 +11,10 @@ Use the Task Description icons for the following actions: | Icon | Description | |------------------------------------|-------------------------------| |**Check** | Check the correctness of your answer (for a quiz) or your code solution (for a programming task)| -| **Run** | Run your code (for a theoretical tasks)| +| **Run** | Run your code (for a theoretical task)| | | Go to the previous task | | or **Next** | Go to the next task| -| | Discard all the changes you’ve made in the task, and start over| +| | Discard all the changes you’ve made in the task and start over| | | View the task page on Stepik and leave a comment| |Peek Solution... | Reveal the correct answer and show the diff| diff --git a/Object Oriented Programming/Object Oriented Programming/task.md b/Object Oriented Programming/Object Oriented Programming/task.md index aa182a8..f705e06 100644 --- a/Object Oriented Programming/Object Oriented Programming/task.md +++ b/Object Oriented Programming/Object Oriented Programming/task.md @@ -2,7 +2,7 @@ ## Abstract Classes Consider the task of writing a class for sets of integers with -the following operations. +the following operations: abstract class IntSet { def incl(x: Int): IntSet @@ -21,7 +21,7 @@ the operator `new`. Let's consider implementing sets as binary trees. -There are two types of possible trees: a tree for the empty set, and +There are two types of possible trees: a tree for an empty set and a tree consisting of an integer and two sub-trees. Here are their implementations: @@ -44,9 +44,9 @@ Here are their implementations: else this } -`Empty` and `NonEmpty` both *extend* the class `IntSet`. +Both `Empty` and `NonEmpty` *extend* the class `IntSet`. -This implies that the types `Empty` and `NonEmpty` *conform* to the type `IntSet` +This implies that the types `Empty` and `NonEmpty` *conform* to the type `IntSet`: - an object of type `Empty` or `NonEmpty` can be used wherever an object of type `IntSet` is required. @@ -71,8 +71,8 @@ The definitions of `contains` and `incl` in the classes `Empty` and `NonEmpty` *implement* the abstract functions in the base trait `IntSet`. -It is also possible to *redefine* an existing, non-abstract -definition in a subclass by using `override`. +It is also possible to *redefine* an existing non-abstract +definition in a subclass by using `override`: abstract class Base { def foo = 1 @@ -121,4 +121,4 @@ Can we implement one concept in terms of the other? - Higher-order functions in terms of objects? ## Exercise -Complete the `nonEmptyExample` for it to return a new `NonEmpty` containing 7 and two `Empty` objects. \ No newline at end of file +Complete the `nonEmptyExample` to make it return a new `NonEmpty` containing 7 and two `Empty` objects. \ No newline at end of file diff --git a/Object Oriented Programming/Rational Arithmetic/task.md b/Object Oriented Programming/Rational Arithmetic/task.md index b411a74..42d60b8 100644 --- a/Object Oriented Programming/Rational Arithmetic/task.md +++ b/Object Oriented Programming/Rational Arithmetic/task.md @@ -34,7 +34,7 @@ In Scala, we do this by defining a *class*: This definition introduces two entities: - - A new *type*, named `Rational`. + - A new *type*, named `Rational`; - A *constructor* `Rational` to create elements of this type. Scala keeps the names of types and values in *different namespaces*. @@ -45,7 +45,7 @@ So there's no conflict between the two definitions of `Rational`. We call the elements of a class type *objects*. We create an object by prefixing an application of the constructor of -the class with the operator `new`. +the class with the operator `new`: new Rational(1, 2) @@ -92,8 +92,8 @@ abstraction in the data abstraction itself. Such functions are called *methods*. -Rational numbers now would have, in addition to the functions `numer` -and `denom`, the functions `add`, `sub`, +Rational numbers now, in addition to the functions `numer` +and `denom`, would have the functions `add`, `sub`, `mul`, `div`, `equal`, `toString`. Here's a possible implementation: @@ -120,7 +120,7 @@ Here is how one might use the new `Rational` abstraction: ### Data Abstraction -In the above example rational numbers weren't always +In the above example, rational numbers weren't always represented in their simplest form. One would expect the rational numbers to be *simplified*: @@ -196,7 +196,7 @@ Add the functions `less` and `max` to the class `Rational`. Note that a simple name `x`, which refers to another member of the class, is an abbreviation of `this.x`. Thus, an equivalent -way to formulate `less` is as follows. +way to formulate `less` is as follows: def less(that: Rational) = this.numer * that.denom < that.numer * this.denom @@ -220,7 +220,7 @@ with the given message string. Besides `require`, there is also `assert`. -Assert also takes a condition and an optional message string as parameters. E.g. +Assert also takes a condition and an optional message string as parameters. E.g.: val x = sqrt(y) assert(x >= 0) @@ -228,7 +228,7 @@ Assert also takes a condition and an optional message string as parameters. E.g. Like `require`, a failing `assert` will also throw an exception, but it's a different one: `AssertionError` for `assert`, `IllegalArgumentException` for `require`. -This reflects a difference in intent +This reflects a difference in intent: - `require` is used to enforce a precondition on the caller of a function. - `assert` is used as to check the code of the function itself. @@ -240,9 +240,9 @@ is called the *primary constructor* of the class. The primary constructor: - - takes the parameters of the class + - takes the parameters of the class; - and executes all statements in the class body - (such as the `require` a couple of slides back). + (such as the `require` in one of the examples above). ### Auxiliary Constructors @@ -271,7 +271,7 @@ are evaluated like the arguments of a normal function. That's it. The resulting expression, say, `new C(v1, …, vn)`, is already a value. -Now suppose that we have a class definition, +Now suppose that we have a class definition class C(x1, …, xn) { … @@ -295,9 +295,9 @@ How is the following expression evaluated? The following three substitutions happen: - the substitution of the formal parameters `y1, …, ym` of the function `f` by the - arguments `w1, …, wm`, + arguments `w1, …, wm`; - the substitution of the formal parameters `x1, …, xn` of the class `C` by the class - arguments `v1, …, vn`, + arguments `v1, …, vn`; - the substitution of the self reference `this` by the value of the object `new C(v1, …, vn)`. @@ -316,9 +316,8 @@ In Scala, we can eliminate this difference because operators can be used as iden Thus, an identifier can be: - - *Alphanumeric*: starting with a letter, followed by a sequence of letters or numbers - - *Symbolic*: starting with an operator symbol, followed by other operator symbols. - - The underscore character `'_'` counts as a letter. + - *Alphanumeric*: starting with a letter, followed by a sequence of letters or numbers (the underscore character `'_'` counts as a letter); + - *Symbolic*: starting with an operator symbol, followed by other operator symbols; - Alphanumeric identifiers can also end in an underscore, followed by some operator symbols. Examples of identifiers: @@ -327,7 +326,7 @@ Examples of identifiers: ### Operators for Rationals -So, here is a more natural definition of class `Rational`: +So, here is a more natural definition of the class `Rational`: class Rational(x: Int, y: Int) { private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) @@ -344,7 +343,7 @@ So, here is a more natural definition of class `Rational`: ... } -and then rational numbers can be used like `Int` or `Double`: +Then, rational numbers can be used like `Int` or `Double`: val x = new Rational(1, 2) val y = new Rational(1, 3) diff --git a/Object Oriented Programming/Traits/task.md b/Object Oriented Programming/Traits/task.md index 355347d..c1df22d 100644 --- a/Object Oriented Programming/Traits/task.md +++ b/Object Oriented Programming/Traits/task.md @@ -6,10 +6,10 @@ In Scala, a class can only have one superclass. But what if a class has several natural supertypes to which it conforms or from which it wants to inherit code? -Here, you could use `trait`s. +In such case, you could use `trait`s. A trait is declared like an abstract class, just with `trait` instead of -`abstract class`. +`abstract class`: trait Planar { def height: Int @@ -50,8 +50,8 @@ There is no value of type `Nothing`. Why is that useful? - - To signal abnormal termination - - As an element type of empty collections + - It signals abnormal termination. + - It may be an element type of empty collections. ### The Null Type @@ -60,7 +60,7 @@ Every reference class type also has `null` as a value. The type of `null` is `Null`. `Null` is a subtype of every class that inherits from `Object`; it is -incompatible with subtypes of `AnyVal`. +incompatible with the subtypes of `AnyVal`. val x = null // x: Null val y: String = null // y: String @@ -72,3 +72,6 @@ The following `Reducer` abstract class defines how to reduce a list of values into a single value by starting with an initial value and combining it with each element of the list. +Complete the implementation of the objects `Product` and `Sum`, which +inherit from `Reducer`, so that the first `println` statement prints `24` +and the second one – `10`. diff --git a/Polymorphic Types/Covariance/task.md b/Polymorphic Types/Covariance/task.md index f751643..28a221c 100644 --- a/Polymorphic Types/Covariance/task.md +++ b/Polymorphic Types/Covariance/task.md @@ -4,26 +4,24 @@ There's another interaction between subtyping and type parameters we need to consider. -Consider the following type modeling a field containing an animal: +Consider the following type modeling a field that contains an animal: trait Field[A] { def get: A // returns the animal that lives in this field } -Given +Given: Zebra <: Mammal -is +Would the following be true? Field[Zebra] <: Field[Mammal] -? - Intuitively, this makes sense: a field containing a zebra is a special case of a field containing an arbitrary mammal. -We call types for which this relationship holds *covariant* +We call the types for which this relationship holds *covariant* because their subtyping relationship varies with the type parameter. Does covariance make sense for all types, not just for `Field`? @@ -34,14 +32,14 @@ For perspective, let's look at arrays in Java (and C#). Reminder: - - An array of `T` elements is written `T[]` in Java. - - In Scala we use parameterized type syntax `Array[T]` to refer to the same type. + - An array of `T` elements is written as `T[]` in Java. + - In Scala, we use the parameterized type syntax `Array[T]` to refer to the same type. Arrays in Java are covariant, so one would have: Zebra[] <: Mammal[] -But covariant array typing causes problems. +However, covariant array typing causes problems. To see why, consider the Java code below: @@ -50,27 +48,27 @@ To see why, consider the Java code below: mammals[0] = new Giraffe() // Allowed because a `Giraffe` is a subtype of `Mammal` Zebra zebra = zebras[0] // Get the first `Zebra` … which is actually a `Giraffe`! -It looks like we assigned in the last line a `Giraffe` to a -variable of type `Zebra`! +It looks like we assigned a `Giraffe` to a +variable of type `Zebra` in the last line! What went wrong? #### The Liskov Substitution Principle -The following principle, stated by Barbara Liskov, tells us when a +The following principle stated by Barbara Liskov tells us when a type can be a subtype of another. If `A <: B`, then everything one can to do with a value of type `B` one should also be able to do with a value of type `A`. -The problematic array example would be written as follows in Scala: +The problematic array example would be written in Scala as follows: val zebras: Array[Zebra] = Array(new Zebra) val mammals: Array[Mammal] = zebras mammals(0) = new Giraffe val zebra: Zebra = zebras(0) -If you try to compile this example you will get a compile error at line 2: +If you try to compile this example, you will get a compile error at line 2: type mismatch; found : Array[Zebra] @@ -78,29 +76,29 @@ If you try to compile this example you will get a compile error at line 2: ## Variance -We have seen that some types should be covariant whereas +We have seen that some types should be covariant, whereas others should not. Roughly speaking, a type that accepts mutations of its elements should not be covariant. -But immutable types can be covariant, if some conditions +Meanwhile, immutable types can be covariant if some conditions on methods are met. ## Definition of Variance -Say `C[T]` is a parameterized type and `A`, `B` are types such that `A <: B`. +Say `C[T]` is a parameterized type, and `A` and `B` are types such that `A <: B`. In general, there are *three* possible relationships between `C[A]` and `C[B]`: - - `C[A] <: C[B]`, `C` is *covariant*, - - `C[A] >: C[B]`, `C` is *contravariant*, + - `C[A] <: C[B]`, `C` is *covariant*; + - `C[A] >: C[B]`, `C` is *contravariant*; - neither `C[A]` nor `C[B]` is a subtype of the other, `C` is *nonvariant*. Scala lets you declare the variance of a type by annotating the type parameter: - - `class C[+A] { … }`, `C` is *covariant*, - - `class C[-A] { … }`, `C` is *contravariant*, + - `class C[+A] { … }`, `C` is *covariant*; + - `class C[-A] { … }`, `C` is *contravariant*; - `class C[A] { … }`, `C` is *nonvariant*. ### Typing Rules for Functions @@ -134,27 +132,26 @@ an example of a contravariant type. ### Variance Checks -We have seen in the array example that the combination of covariance with +In the array example, we have seen that the combination of covariance with certain operations is unsound. In the case of `Array`, the problematic combination is: - - the covariant type parameter `T` - - which appears in parameter position of the method `update`. + - the covariant type parameter `T` which appears in the parameter position of the method `update`. The Scala compiler will check that there are no problematic combinations when compiling a class with variance annotations. -Roughly, +Roughly speaking, - *covariant* type parameters can only appear in method results. - *contravariant* type parameters can only appear in method parameters. - *invariant* type parameters can appear anywhere. -The precise rules are a bit more involved, fortunately the Scala compiler performs them for us. +The precise rules are a bit more involved; fortunately, the Scala compiler performs them for us. #### Variance-Checking the Function Trait -Let's have a look again at Function1: +Let's have a look at `Function1` again: trait Function1[-T, +U] { def apply(x: T): U @@ -165,13 +162,13 @@ Here, - `T` is contravariant and appears only as a method parameter type - `U` is covariant and appears only as a method result type -So the method is checks out OK. +So the method checks out OK. ## Making Classes Covariant Sometimes, we have to put in a bit of work to make a class covariant. -Consider adding a `prepend` method to `Stream` which prepends a given +Consider adding a `prepend` method to `Stream`, which prepends a given element, yielding a new stream. A first implementation of `prepend` could look like this: @@ -186,14 +183,14 @@ Why does the above code not type-check? `prepend` fails variance checking. -Indeed, the compiler is right to throw out `Stream` with `prepend`, -because it violates the Liskov Substitution Principle: +Indeed, the compiler is right to throw out `Stream` with `prepend` +because it violates the Liskov Substitution Principle. -Here's something one can do with a stream `mammals` of type `Stream[Mammal]`: +Here's something one can do with the stream `mammals` of type `Stream[Mammal]`: mammals.prepend(new Giraffe) -But the same operation on a list `zebras` of type +However, the same operation on the list `zebras` of type `Stream[Zebra]` would lead to a type error: zebras.prepend(new Giraffe) @@ -214,10 +211,10 @@ We can use a *lower bound*: ``` Stream.cons(elem, this) ``` -This passes variance checks, because: +This does pass variance checks because: - - covariant type parameters may appear in lower bounds of method type parameters - - contravariant type parameters may appear in upper bounds of method + - covariant type parameters may appear in the lower bounds of method type parameters; + - contravariant type parameters may appear in the upper bounds of a method. ## Exercise diff --git a/Polymorphic Types/Polymorphic Types/task.md b/Polymorphic Types/Polymorphic Types/task.md index 4dd2172..b1f8ace 100644 --- a/Polymorphic Types/Polymorphic Types/task.md +++ b/Polymorphic Types/Polymorphic Types/task.md @@ -10,7 +10,7 @@ Remember the definition of `IntSet` (in section [Object Oriented Programming](co It seems too narrow to define only sets with `Int` elements. -We'd need another class hierarchy for `Double` lists, and so on, +We'd need another class hierarchy for `Double` lists and so on, one for each possible element type. We can generalize the definition using a *type parameter*: @@ -26,13 +26,13 @@ We can generalize the definition using a *type parameter*: … } -Type parameters are written in square brackets, e.g. `[A]`. +Type parameters are written in square brackets, e.g., `[A]`. ## Generic Functions Like classes, functions can have type parameters. -For instance, here is a function that creates a set consisting of a single element. +For instance, here is a function that creates a set consisting of a single element: def singleton[A](elem: A) = new NonEmpty[A](elem, new Empty[A], new Empty[A]) @@ -60,9 +60,9 @@ before evaluating the program. This is also called *type erasure*. -Languages that use type erasure include Java, Scala, Haskell, ML, OCaml. +Languages that use type erasure include Java, Scala, Haskell, ML, and OCaml. -Some other languages keep the type parameters around at run time, these include C++, C#, F#. +Some other languages keep the type parameters around at run time; these include C++, C#, and F#. ## Polymorphism @@ -75,7 +75,7 @@ In programming, it means that We have seen two principal forms of polymorphism: - - subtyping: instances of a subclass can be passed to a base class + - subtyping: instances of a subclass can be passed to a base class; - generics: instances of a function or class are created by type parameterization. The remaining subsections compare their interaction. @@ -101,5 +101,5 @@ Consider the following class hierarchy: ## Exercise -Complete the following implementation of the `size` function that returns +Complete the following implementation of the `size` function, which returns the size of a given list. diff --git a/Polymorphic Types/Type Bounds/task.md b/Polymorphic Types/Type Bounds/task.md index c3832f1..6e0a7fb 100644 --- a/Polymorphic Types/Type Bounds/task.md +++ b/Polymorphic Types/Type Bounds/task.md @@ -1,6 +1,6 @@ ## Type Bounds -Consider the method `selection` that takes two animals as parameters +Consider the method `selection`, which takes two animals as parameters and returns the one with the highest `fitness` value: What would be the best type you can give to `selection`? Maybe: @@ -21,7 +21,7 @@ A way to express this is: Here, “`<: Animal`” is an *upper bound* of the type parameter `A`. -It means that `A` can be instantiated only to types that conform to `Animal`. +It means that `A` can be instantiated only to the types that conform to `Animal`. Generally, the notation @@ -34,11 +34,11 @@ You can also use a lower bound for a type variable. A >: Reptile -The type parameter `A` that can range only over *supertypes* of `Reptile`. +The type parameter `A` can range only over the *supertypes* of `Reptile`. So `A` could be one of `Reptile`, `Animal`, `AnyRef`, or `Any`. -(We will see later on where lower bounds are useful). +(We will see where lower bounds are useful later on.) ### Mixed Bounds diff --git a/Standard Library/Common Operations on Lists/task.md b/Standard Library/Common Operations on Lists/task.md index c97833e..9bad934 100644 --- a/Standard Library/Common Operations on Lists/task.md +++ b/Standard Library/Common Operations on Lists/task.md @@ -21,7 +21,7 @@ results into a single list using `flatMap`: ### Optional Values We represent an optional value of type `A` with the type `Option[A]`. -This is useful to implement, for instance, partially defined +This is useful to implement, for instance, in partially defined functions: // The `sqrt` function is not defined for negative values diff --git a/Standard Library/Error Handling/task.md b/Standard Library/Error Handling/task.md index b43a122..89b27db 100644 --- a/Standard Library/Error Handling/task.md +++ b/Standard Library/Error Handling/task.md @@ -22,8 +22,8 @@ the reason for the failure: Like options and lists, `Try[A]` is an algebraic data type, so it can be decomposed using pattern matching. -`Try[A]` also have `map`, `filter` and `flatMap`. They behave the same -as with `Option[A]`, except that any exception that is thrown +`Try[A]` also has `map`, `filter` and `flatMap`. They behave the same +as with `Option[A]`, except that any exception thrown during their execution is converted into a `Failure`. ### Either @@ -45,7 +45,7 @@ not converted into failures. ### Manipulating `Either[A, B]` Values Since Scala 2.12, `Either` has `map` and `flatMap`. These methods -transform the `Right` case only. We say that `Either` is “right biased”: +transform the `Right` case only. We say that `Either` is “right-biased”: Right(1).map((x: Int) => x + 1) shouldBe Right(2) Left("foo").map((x: Int) => x + 1) shouldBe Left("foo") @@ -60,4 +60,4 @@ specify which “side” (`Left` or `Right`) you wanted to `map`. ## Exercise -Complete the ```tripleEither()``` function, so it maps the successful results with the ```triple()``` function. \ No newline at end of file +Complete the ```tripleEither()``` function so that it maps the successful results with the ```triple()``` function. \ No newline at end of file diff --git a/Standard Library/List/task.md b/Standard Library/List/task.md index 02d7ff1..f0f074a 100644 --- a/Standard Library/List/task.md +++ b/Standard Library/List/task.md @@ -3,15 +3,15 @@ The list is a fundamental data structure in functional programming. -A list having `x1`, …, `xn` as elements is written `List(x1, …, xn)`: +A list having `x1`, …, `xn` as elements is written as `List(x1, …, xn)`: val fruit = List("apples", "oranges", "pears") val nums = List(1, 2, 3, 4) val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) val empty = List() - - Lists are immutable --- the elements of a list cannot be changed, - - Lists are recursive (as you will see in the next subsection), + - Lists are immutable – the elements of a list cannot be changed. + - Lists are recursive (as you will see in the next subsection). - Lists are *homogeneous*: a list is intended to be composed of elements that all have the same type. The type of a list with elements of type `T` is written `List[T]`: @@ -56,7 +56,7 @@ So the expression above is equivalent to: It is possible to decompose lists with pattern matching: - - `Nil`: the `Nil` constant, + - `Nil`: the `Nil` constant; - `p :: ps`: A pattern that matches a list with a `head` matching `p` and a `tail` matching `ps`. ``` @@ -75,9 +75,9 @@ It is possible to decompose lists with pattern matching: ``` ## Exercise: Sorting Lists -Suppose we want to sort a list of numbers in ascending order: +Suppose we want to sort a list of numbers in ascending order. - - One way to sort the list `List(7, 3, 9, 2)` is to sort the + - One way to sort the list `List(7, 3, 9, 2)` is to first sort the tail `List(3, 9, 2)` to obtain `List(2, 3, 9)`. - The next step is to insert the head `7` in the right place to obtain the result `List(2, 3, 7, 9)`. diff --git a/Structuring Information/Defining Alternatives With Sealed Traits/task.md b/Structuring Information/Defining Alternatives With Sealed Traits/task.md index 75cfbc5..9cf1875 100644 --- a/Structuring Information/Defining Alternatives With Sealed Traits/task.md +++ b/Structuring Information/Defining Alternatives With Sealed Traits/task.md @@ -4,7 +4,7 @@ If we look at the introductory picture, we see that musical symbols can be either *notes* or *rests* (but nothing else). -So, we want to introduce the concept of *symbol*, as something +So, we want to introduce the concept of *symbol* as something that can be embodied by a fixed set of alternatives: a note or rest. We can express this in Scala using a *sealed trait* definition: @@ -33,10 +33,10 @@ to do so: } The above `match` expression first checks if the given `Symbol` is a -`Note`, and if it is the case it extracts its fields (`name`, `duration` +`Note`, and if it is the case, it extracts its fields (`name`, `duration`, and `octave`) and evaluates the expression at the right of the arrow. Otherwise, it checks if the given `Symbol` is a `Rest`, and if it -is the case it extracts its `duration` field, and evaluates the +is the case, it extracts its `duration` field and evaluates the expression at the right of the arrow. When we write `case Rest(duration) => …`, we say that `Rest(…)` is a @@ -82,7 +82,7 @@ def nonExhaustiveDuration(symbol: Symbol): String = } } ``` -If we try to run the above code to see how the compiler informs us that +We can try to run the above code to see how the compiler informs us that we don’t handle all the cases in `nonExhaustiveDuration`. ### Equals @@ -92,7 +92,7 @@ aggregate values, comparing case class instances compares their values. ## Exercise -Complete the `Rest` class declaration for it to extend Symbol. +Complete the `Rest` class declaration so that it extends `Symbol`. Complete the `caseClassEquals` and `symbolDuration` method definitions.
caseClassEquals, check if case class instances are equal.Int method toHexString, which returns
+Int method toHexString, which returns
the hexadecimal form of the specified integer value.contains() method is utilized to check whether a certain element is present in the range/list/set or not.drop(n) method returns all the elements of the collection except the first n ones.