1. Functions and Data
In Scala, a class ia defined like this:
This definition introduces two entities:
We call the elements of a class type
We create an object by prefixing an application of the constructor of the class with the operator
1.3 Members of an Object
Objects of the class
Rational have two members,
We select the members of an object with the infix operator ‘
.’ (like in Java).
1.4 Rational Arithmetic
We can now define the arithmetic functions that implement the standard rules.
1.5 Implementing Rational Arithmetic
One can go further and also package functions operating on a data abstraction in the data abstraction itself.
Such functions are called methods.
Remark: the modifier
override declares that toString redefines a method that already exists (in the class
(1) In your worksheet, add a method
neg to class
Rational that is used like this:
(2) Add a method
sub to subtract two rational numbers.
(3) With the values of x , y , z as given in the previous slide, what is the result of
x - y - z?
2. More Fun with Rationals
2.1 Data Abstraction
One would expect the rational numbers to be simplified:
- reduce them to their smallest numerator and denominator by dividing both with a divisor.
We could implement this in each rational operation, but it would be easy to forget this division in an operation.
A better alternative consists of simplifying the representation in the class when the objects are constructed:
It is also possible to call gcd in the code of
This can be advantageous if it is expected that the functions
denom are called infrequently.
It is equally possible to turn
vals, so that they are computed only once:
This can be advantageous if the functions
denom are called often.
2.2 The Client’s View
Clients observe exactly the same behavior in each case.
This ability to choose different implementations of the data without affecting clients is called data abstraction.
It is a cornerstone of software engineering.
2.3 Self Reference
On the inside of a class, the name
this represents the object on which the current method is executed.
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.
Let’s say our
Rational class requires that the denominator is positive.
require is a predefined function.
It takes a condition and an optional message string.
If the condition passed to require is
false , an
IllegalArgumentException is thrown with the given message string.
require , there is also
assert also takes a condition and an optional message string as parameters.
require , a failing assert will also throw an exception, but it’s a different one:
This reflects a difference in intent
requireis used to enforce a precondition on the caller of a function.
assertis used as to check the code of the function itself.
In Scala, a class implicitly introduces a
constructor. This one is called the primary constructor of the class.
The primary constructor
- takes the parameters of the class
- and executes all statements in the class body (such as the require a couple of slides back).
2.7 Auxiliary Constructors
Scala also allows the declaration of auxiliary constructors.
These are methods named
3. Evaluation and Operators
3.1 Classes and Substitutions
We previously defined the meaning of a function application using a computation model based on substitution. Now we extend this model to classes and objects.
Question: How is an instantiation of the class new
C(e_1 ,..., e_m) evaluted?
Answer: The expression arguments
e_1 ,..., e_m are evaluated like the arguments of a normal function. That’s it.
The resulting expresion, say, new
C(v_1 ,..., v_m), is already a value.
Now suppose that we have a class definition,
- The formal parameters of the class are
x_1 ,..., x_m.
- The class defines a method
fwith formal parameters
y_1 ,..., y_n.
(The list of function parameters can be absent. For simplicity, we have omitted the parameter types.)
Question: How is the following expression evaluated?
Answer: The expression
new C(v_1 ,..., v_m).f(w_1 ,..., w_n) is rewritten to:
There are three substitutions at work here:
- the substitution of the formal parameters
y_1 ,..., y_nof the function f by the arguments
w_1 ,..., w_n,
- the substitution of the formal parameters
x_1 ,..., x_mof the class C by the class arguments
v_1 ,..., v_m,
- the substitution of the self reference this by the value of the object
new C(v_1 ,..., v_n).
3.2 Object Rewriting Examples
In principle, the rational numbers defined by Rational are as natural as integers.
But for the user of these abstractions, there is a noticeable difference:
- We write
x + y, if
yare integers, but
- We write
sare rational numbers.
In Scala, we can eliminate this difference. We procede in two steps.
Step 1: Infix Notation
Any method with a parameter can be used like an infix operator.
It is therefore possible to write
Step 2: Relaxed Identifiers
Operators can be used as identifiers.
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 identifiers can also end in an underscore, followed by some operator symbols.
Examples of identifiers:
3.4 Operators for Rationals
Now rational numbers can be used like
3.5 Precedence Rules
The precedence of an operator is determined by its first character.
The following table lists the characters in increasing order of priority precedence: