0:00
To recap, the fundamental types you've seen in the previous sessions were
primitive types, functions and classes. It's fair to ask whether these three forms
of types are necessary or whether we could express maybe our types as classes?
We'll discuss that question in this section and the next one.
First in this session we'll look at primitive types and see how they can be
represented as classes. So, so far we've seen Scala as a
functional programming language. I'm now going to argue that Scala is
actually also object oriented, and that it's object oriented and that it's object
oriented in a very pure form. So a pure object oriented language, the
definition would be one in which every value is an object, and also every
operation is then essentially a method call on some object.
If the language is based on classes this means that the type of each value would be
a class. So now lets ask ourselves, is color pure
object oriented language? At first glance there seemed to be some
exceptions. For instance we have seem primitive types
such as [UNKNWON] and double of these objects.
We've also seen functions. They don't look like objects at first
glance, but lets look somewhat closer. Let's look at the primitive types first.
So the standard classes such as Int or booleans conceptually they actually don't
receive any special treatment in Scala. You can treat them just like other classes
you find in the packaged Scala subclasses of class eneval.
It's true that for reasons of efficiency in the Int this scala compiler will
represent the values of types scala Int say by 32-bit integers, the values of type
scala booleans by java booleans and so on. But that, you could treat simply as an
optimization and as a measure to improve the interoperability between Scala code
and Java code. Conceptually, these things can be treated
just like normal classes. And instances of integer can, or Boolean
can be treated just like normal objects. So let's see how this could be done.
You know that the Boolean type maps to the JVMs primitive type Boolean.
But one can define it as a class without any changes in the user code from first
principles without resort to primitive Booleans.
Here's how this could be done. We put it in a package idealized Scala to
make clear that's actually, the class Boolean that is not the same as the
primitive type that we use in normal Scala.
That class Boolean here, then. It would have one abstract method which we
call ifThenElse. It's a parameterized method.
It, it contains a type parameter T and the then part,
Which I- I'm calling here simply T, and an Else part.
Both the Then part and the Else part take an expression of type T.
And the result of the ifThenElse, then, would be T.
So the idea would be that I would write, instead of if some condition, Boolean
condition. And then some then expression Else, Else
expression. I would translate that to the if then else
method call of my condition, So it would be condition.ifThenElse,
Then whatever type t-e and e-e would have I'm leaving that out.
And I'll pass t-e and e-e as the two arguments to the ifThenElse method.
That will be the translation for simulating Booleans.
Now once I have ifThenElse, how would I define the other operators on Booleans
like the conjunction and disjunction, double am- ampersand.
Actually turns out that all the other operations on Booleans can be defined in
terms of ifThenElse. So let's look at && for instance.
The & operation would take another boolean, or rather an expression of type
boolean, because we know that it will be evaluated in a short-circuited fashion.
And it would then call ifThenElse with x and false.
So what this means is that, if the Boolean itself is true, then you would return the
second argument, The Boolean argument x.
On the other hand, if the left hand operant, the Boolean here itself is false,
then the result is immediately false. And the same tricks you can also apply to
all the other methods on Booleans. So for instance, for the disjunction here,
you'd again take the call by name parameter x, that's the right hand
operand. And it says, well, if the Boolean itself
is true, then the result is immediately true.
Whereas if the, the boolean itself is false, then the result is whatever the
right hand side argument is. The third one would be the not-negational
operation, which we write here as unary-not.
So unary-not would simply be implemented as ifThenElse false, true, that means if
the Boolean itself is true, we return false.
If the Boolean itself is false, we return true.
5:31
Let's look at the equality and disequality next.
We would say well, if, to find out whether two Booleans are equal, we can go to the
ifThenElse method. And say, well, if the Boolean itself is
true, then the result is whatever the argument is.
So, if the argument is also true, then the result would be true.
If the argument is false, then the equals test would be false.
So the result of the equals test is exactly the same as the value of the
argument. On the other hand, if the current Boolean
is false then the result would be the negation of the argument.
Let's again go through the, the possibilities.
So we say the current Boolean is false, the argument is false then the equality
test should give true. And, here we would have the negation
operator on the argument false, so we would get true, that's correct.
If the argument on the other hand is TRUE, the, the result should be FALSE.
False equals to TRUE get FALSE and again, here TRUE.unary bang gives FALSE.
And, for not equal, it is completely analogous.
6:47
So once we have that outline, we still have to define the Boolean constants false
and true. By now, it should be clear that the, this
false and this true. They can't be the primitive Booleans.
Because we passed them to an ifThenElse together with one of our idealized
Booleans. So false and true must themselves be
constants of type idealized Scala Boolean. So here's how we would define them.
Each of those of would be an object, and for the true constants, the only thing I
need to do is define what the definition of ifThenElse should be for that constant.
So what is the definition of if , true, then.then Expression Else, Else
expression. Well, we've seen the rewrite tool.
We've decided that would be then, Then expressions..
So what you see here is precisely the implementation of that rewrite tool.
We say ifThenElse so for Then expression and then Else expression gives us the Then
part. And conversely ifThenElse of a Then part
and an Else part in the false constant would give us the Else part.
That's all there is to it. So, let's do an exercise.
Let's complete the definition of Booleans with a, a comparison operator that shows
how we would compare two Booleans with a less than operation.
And we assume for this that, the order is that false, the value false would be less
than the value true. So, let's see how we would do that.
So what we are after is in our class Boolean.
8:35
You want to define to a method def less, which takes an x of type Boolean And which
calls if ifThenElse because that's all we ever can do with some arguments.
So what should the arguments be? Well, lets go through the motions if the
current Boolean is true then we go in the Then part.
So the left-hand side is true then obviously the left-hand side can never be
less than the other one. So we would return false.
On the other hand, if the current Boolean is false, then the less operation yields
true. If the argument is true, false if it is
false. So it is the same as the argument
expression, x. So here, you have the solution for the
less operation. And of course, all the other comparison
operators less than or = is greater than or equals greater would behave in
analogous ways. But now that we have boolean, let's look
at class int. Don't worry, I won't give you a full
implementation of class int that will be a bit too involved.
But here's at least what's a partial specification what methods it would
support. Let's start with plus.
So, the int class would have a method plus that takes an other int and yields an int.
But actually, you can also write an expression such as one + 2.0.
And you would expect the result to be a double.
So there's another operation that takes a double, and yields a double end for the
motor operations, for floats and longs as well.
In each case, the result type is the high, the, the greater type of the left hand
operand, or the right hand operand. So, now we have plus and you do exactly
the same thing for minus for multiplication, for division and modulo.
The one thing I haven't said explicitly yet is that Scala has overloading very
similar to what Java had. So, it's perfectly possible to have four
versions of the plus method that all take different arguments.
The Scala compiler would then figure out what method to, to call based on the type
of the actual arguments in the method call.
Other operations we see on this slide are the left shift operation, which takes
shift count and gives you back an int, and bit wise and operations that work with
either long arguments or int arguments. And finally, there are equality operations
that would also then take a range of arguments like ints, doubles, floats, and
longs, and give us Boolean results. So you see that all the operations on ints
can be expressed as methods, if you just look at the signatures.
But, the question remains, can we also implement them without resorting to
primitive integers, to primitive types. So, the question is, can we maybe
implement all of programming using just objects and functions?
No primitive types at all. So we want to do that for class int but as
an exercise I would like you to try that for something slightly simpler a class of
natural numbers. So that's just integers starting from zero
and positive integers but not negative integers and I also assume that in this
class has only a sub-set of the operations that you would assume for int's.
So, the methods I'd like you to implement are five.
The first is, the test is zero, which tests whether a given natural number is
zero. The second is predecessor, which should
return for a positive natural number, the one before that, and should throw an
exception if the natural number is zero. The successor, which gives you the next
natural number after the current natural number.
And finally, addition and subtraction operations on natural numbers.
Again, for the subtraction, you, I would assume that the code will throw an
exception if the result is negative. So, you should not use the standard
primitive classes in your implementation neither int nor any other primitive class.
Instead you should implement a sub object in the subclass.
Call it zero and successor. So, the zero object would represent the
number zero, where is the successor of n would represent the natural number that's
one bigger than the argument and here. I should say that this quiz is quite a bit
more involved than previous quizzes. So, you'll need some time to get it right.
But, it's worth it for, I believe for the insight that you will gain from it.
15:12
So we need to implement its operations. Is zero, obviously is false The
predecessor of the current number would be.
Well, it would be the number that we pass into, the successor because the successor
represents the number one plus the given number here.
So it's predecessor is N. The successor would be new successor of
this. That's just like in the empty number.
So what we see here is that actually, we have some duplication where we say.
No matter whether the number is zero or successor, the successor of this number is
always new successor of this. So what we can do is, we can actually do a
refactoring, and move that up here. In the base class, because it holds
uniformly for both of the subclasses. So let's see whether we can do the same
thing for plus and minus. So let's turn to addition.
Zero plus some natural number would be, well, just the natural number we pass
here. That was easy.
Let's try to do the same thing for successor zero plus some natural number
Not would be. Well what, what do we do in that case?