This section is titled Objects Everywhere. The topic of this section is that this model that values our objects and operations or methods can really be applied quite universally in Scala. We say a pure object-oriented language is one in which every value is an object. If the language has classes, this means that type of each value is a class. Is Scala a pure object oriented language? At first glance, there seem to be some exceptions. For instance, we saw primitive types or function types. But let's take a closer look. A class such as Int or Boolean is represented by the computer quite differently from an object. An object is typically a multi-word record on the heap and an integer and Boolean is just a primitive value that can sit in a register. Conceptually, types such as Int or Boolean don't receive any special treatment in Scala. They're just like the other classes defined in package Scala. It's true that for reasons of efficiency, the Scala compiler represents values of type scala.Int by 32-bit integers and values of type scala.Booleans by Java's Booleans and so on. Whereas, a normal object would be represented as some form of record with multiple fields in the heap of the program execution. For efficiency, the Boolean type maps to the JVM's primitive type, boolean. But conceptually, one could define it as a class from first principles. Here's a way to do it. Let's say we are in a package idealized.scala. We define an abstract class, Boolean, that has a single abstract method called ifThenElse. IfThenElse resembles an if expression. It takes two parts: a then part and an else part, which are both by name parameters. The type is arbitrary. IfThenElse takes a type parameter, T, which is the type of the then part and the else part. We'll get to the other operation shortly. Let's skip ahead and see how ifThenElse would be implemented. The type Boolean would have two concrete objects, true and false, that both extend Boolean. For true, ifThenElse is implemented to always pick the Then part. For false, the ifThenElse would be implemented to always pick the else part. So what we would have as expressions is if we write true.ifThenElse(a, b), that always gives a; and false.ifThenElse(a, b) always would give b. What you see is this ifThenElse method is really the same thing as writing if true then a else b; that's what it encodes. Now that we've seen how ifThenElse is implemented, let's see how we can put it to use. I go back to the previous slide. The definition of the && method on Booleans would then be a takes a by name parameter x and it returns ifThenElse x, false. Let's see how this would work. If we have true &&, let's say false, that's the same thing as the && method applied on the true object, that's just what the infix operator, &&, means. Then if you look it up, then that would be true.ifThenElse, abbreviated like this, (false, false) and that will be false, because either operand is false. On the other hand, if we do true && true, that would be true.ifThenElse (true, false). That would be true because ifThenElse on true picks the first argument, so that's okay. You can write by hand yourself the cases false and false and false and true, and you will find out that the answer is always the right one. Similarly, the OR operation with an ifThenElse, true, and x, the negation would be ifThenElse, false and true. That means if the Boolean in question is true, then it returns false. If it's false, it returns true. So equals and not equals can also be defined in ifThenElse, and here you find their definitions. I invite you to try that out for yourself that you get the right values each time. Here is an exercise. Provide an implementation of an implementation operator for class idealized.scala.Boolean. The implementation should be true if A implies B. So A is either false or B is true. Let's see what that would look like. We can define it as an extension method, def equals-equals arrow between two Booleans, and that would say, "ifThenElse, y true." If A is false, then we return the second part, that's true. If A or x here is true, then y must be true also, so we return the first part. You can see that you can define any operation on Booleans by instantiating ifThenElse in the right way, where ifThenElse is really just a proxy for the ifThenElse expression that you have with Boolean conditions. Boolean was a simpler class because it had only two possible values, but we can do similar techniques for other classes. Here is a partial specification of the class scala.Int. Int would have methods plus that takes an Int, add an Int and give you back an Int. But you can have several overloaded variance of such methods, so you can also add an Int to a Long giving a Long, or add an Int to a Double, giving a Double. Then you have shift operators, you have bitmask operators, and so on. So you have a whole set of these operators that can all be defined as methods on class Int. Question. Can this class Int be represented as a class from first principles, that means not using primitive ints? To answer this question, it's better to look at a slightly simplified version. We look at a class Nat for just natural numbers, that means numbers that are non-zero. We reduce the set of operations that we look at, we want essentially just those five operations, and isZero operation that returns whether the natural number is zero, the predecessor of the natural number, in case it is not zero, it can throw an exception for zero, the successor of the number, which gives the next higher number, and then we want to implement addition and subtraction on those natural numbers. We should do that from first principles without referring to class Int or any of the other primitive number types. What we want to do instead is we want to have a object Zero, which is a sub-object of class Nat, and we want to have a class successor of n, that is another subclass. So successor of n would be the instance that represents n plus 1 for any number n. This is an open-ended puzzle for you. It's quite a bit more involved than the previous quizzes, but it's quite illuminating if you manage to solve it. I've given the class hierarchy as specified in the worksheet. We have the abstract class Nat with the five methods to implement. Then we have the sub-object Zero and the subclass successor, and I've already added the method definitions, but the method bodies are all the triple question mark, which means they still need to be implemented. Let's start with isZero. IsZero for a natural number? Well, if the natural number is zero, then, obviously, Zero is true, and for the successor, it will be false. What about predecessor? Predecessor is defined only for successors, we don't know what it is for zeros, so for the successor object, it would be n. That means the number from which we took the successor, that's the predecessor of that number. What about successor? Successor of a number would be simply the rapid in successor. So the successor of this number is successor of this, and the successor of Zero is also, literally, successor of Zero, or we can write this because this in this case is zero. What about plus? To add a zero and any other natural number, that, so the result would obviously be that. What about adding a successor to a number that? Here the idea is we can say well that's the successor of n plus that. We take the addition on the number which is one smaller, and we form the successor. That's the same thing. So that leaves subtraction. Subtraction on a zero number, that will be defined only if the that number is in fact zero as well, because otherwise, we would go into negatives which cannot be represented as natural numbers. Let's write that. So again, I've left the case where the number is not zero open with triple question marks, meaning that I don't really know what to do there. For successor, what do I do for successor? I start off in the same way. So if the other number is zero, then this number minus zero is this number. That's fine, and otherwise, what do I do? Otherwise, I take the predecessor n and I subtract from it the predecessor of Nat, and that's it. We can already test the worksheet. Let's define natural numbers. I defined one and two. Let's add them. We don't really see much, so what's missing here is we need to add a toString method so that we can actually make sense of these things. So let's add this method as a sixth method. We don't need to add it here because toString is already implemented in object anyway, so we just have to override it in the two implementations here. So for zero, the toString would say zero, and for successor, I can define toString like this. So I literally write successor and then recursively the toString of the predecessor number. So if I do that, I get an error because I forgot the override. This string needs an override because we've seen that they are already implemented in objects. Now I get something that is more reasonable. I get here the same things that I have defined, and two plus one is three. No big surprise. What do we do with let's say two minus one? Just look at that. That would be one. Also, find what would we get from one minus two, and that's an error. What we would get is a not implemented error. An implementation is missing. In fact, the things that we have left open distributed question mark is a predefined operation in Scala which gives you an exception called a not implemented error. That's a good approximation if you don't want to be bothered defining your own exceptions which in this case is not really relevant for the problem at hand. So we've seen how to construct natural numbers from just three classes, a base class Nat, and then an object zero, and a subclass successor. In fact, that's a well-known concept in mathematics. These numbers are called piano numbers after the Italian logician Giuseppe Peano, and it doesn't stop there. You could now also add multiplication, division, or other operations to natural numbers, and you can then extend the concept to more complicated number types such as integers or rational numbers or any other kind of numbers or really any other kind of data structures. In fact, it's possible to build up the whole of computable mathematics on just this simple basis. Efficiency is another matter of course. These piano numbers in the implementation that we've given are notoriously inefficient because even a simple addition would be linear in the size of the numbers, so the bigger your numbers are, the longer you have to hunt down a chain of successors. So while these things are quite beautiful in their simplicity, they are far from an efficient implementation, but that's not a problem. We can very well keep apart the abstract concepts and the actual implementation. One should be simple and elegant and the other should be efficient and fast.