In this unit we are going to look at decomposition, how to inspect a value and break it up into smaller parts. For instance, support. You want to write a small interpreter for arithmetic expressions. To keep it simple, let's restrict ourselves to just numbers and additions. Expressions can be represented as a class hierarchy with a base trait expr and two subclasses, number and sum, so it would look like this. To treat an expression, it's necessary to know which shape it is. Is it a number, is it a sum? Also to access its components. This brings us to the following object-oriented implementation. Here we see a trait expert with five methods. Two of the methods allow us to find out what kind of expression it is, is number or is sum. The other three allow us to access the components of the expressions. In case the expression is a number, numValue would pull out it's numeric value as an integer. In case it's a sum, leftOp and rightOp, we'll pull out the left and right operands. These five methods now have to be implemented in the two subclasses of expr. Let's look at class number first. A number is defined by an integer value, it extends expression. It is a number, It's not a sum, it's numeric value is to give an integer and it's left and right operand or both undefined, which we model here by throwing an exception. That would be an illegal call if the expression is a number. For sum it goes the other way round. Sum is not a number, but it is a sum. It's numValue would throw an error. It's left operand would give you the first parameter, it's right operand, the second parameter. With that setup, we can now write a methods that operate on expressions. For instance, here's an evaluation function that evaluates an expression to the integer number it's represents. We would have def evaluate, it takes an expression and returns an int. If the expression is a number, then return it's numeric value. Otherwise, if it's a sum, then evaluate the left operand, evaluate the right operand and add the two values. Finally, in case we find another subclass of expression, we throw an error with unknown expression. Now that works, but there are also problems. One problem is that writing all these classification and access of functions quickly becomes tedious. It was already tedious for something as simple as an expression with two subclasses, we will see how much more tedious it gets once we add for the cases two expressions. The other problem is, it's not really very statically safe. There's no static guarantee that you use the right accession functions. Many of the functions throw exceptions when they are called, which means you don't have a compile-time guarantee that your program will execute without exceptions being thrown. To illustrate the first problem, let's study what happens if we add two new expression forums. Say we want to add a product expression that takes two operands and a variable expression that takes a string and represents a variable. We need to add methods for classification and access to all the classes defined above. We have to define the methods for these two new classes. Think about it. If we integrate product and variable into the hierarchy, how many new method definitions do we need? I include here method definitions in product and variable themselves, as well as any new methods that we have to add to sum and a number, but not the methods that we have already shown on the slides. Possible answers would be 9, 10, 19, or is it 25, 35, or 40? To come up with an answer, Let's look at the existing classes first. We need to new methods, is Var and is product in trade expression that have to be implemented everywhere. Furthermore, if it's a variable, we need a method called Var name or something like that to get out so it's name. If it's product, we might need product leftOp and product rightOp. But we could maybe also reuse the leftOp and rightOp operations here. Maybe there are two more Op, Op operations depending on how far we want to separate things. It makes depending on how we count three or five methods in extra that have to be added, that have to be added also to number and also to sum. If we do the sums then depending on how we do things, we need either three or five methods. In addition, pre-existing class and either eight or 10 methods in the new class because the new class has to contain the previous five methods that we've seen already as well. The three existing classes, so that makes nine or 15 and there are two new classes, so that makes 16 or 20. That means if you do the sums, it's either 25 or 35. That means both of these answers would be plausible. It's either 25 or 35. Which means it's quite a lot, even at 25, it's quite a lot. We conclude that this is not very scalable. What else can we do? One thing one could try is using Type Tests and Type Casts, but I find that quite hacky. It's you can test whether a value is of a certain type with the instance of operation. X is instance of t tests whether x is at onetime a value of type T and X as instance of is a cast. It's simply treats an object as an instance of type T and throws a class cast exception if it isn't. These correspond in Java to instance off and to the special custom text that you see here. But the use of type tests and type casts and scholars actually discouraged because they're better alternatives. That's also the reason why we've chosen very long name for them that are intentionally hard to type. Staying with type tests and type casts, here's a formulation of the eval method, but as you can see, it's actually quite, again, it's also potentially unsafe because we don't have usually a guarantee that a type cast which can throw an exception is always protected by a type test of the same, for the same type, that's the case here, but you might easily make a mistake in a larger program. Something which is a more reasonable solution is called object oriented decomposition. For instance, suppose all we want to do is evaluate expressions. Then we could define an abstract method def eval inside the expression trait itself that gets implemented in both of the subclasses. For a number, eval would be an n, and for some eval would be e1.eval plus e2.eval. This looks quite concise. But what happens if we'd like to display expressions now? What we'd have to add a new method in each subclass, let's say def show here. We need one here, and we need one here as well. We need potentially to touch on large number of classes to add some piece of new functionality. We've seen that object oriented decomposition mixes data with operations on the data, the data on the fields and the classes and the operations are the methods. This can be the right thing. If there's a need for encapsulation, we want to hide the data and data abstraction. On the other hand, it increases complexity and adds new dependencies to classes. It increases complexity in the literal sense of the word, where in Latin, complex means plated of woven together. Thus complexity arises from mixing several things together, which is the case for object oriented decomposition. Of course, complexity is not by itself bad and often there's no way to avoid it one way or another. But we should use it only if there's a tangible advantage. A tangible advantage could be that we need encapsulation or data abstraction. Or another advantage of object oriented decomposition is that it makes it very easy to add new kinds of data. Because in that case, we just have to invent a new class and give it all the operations that we have predefined and we are done. Whereas it makes it much harder to invent new operations. If you want to add a new operation like show, we would have to touch all the classes that we currently have. One other limitation of object oriented decomposition is that it works only well if operations are on a single object. To illustrate that, think about what you have to do to simplify expressions. Say using the rule a times b plus a times c becomes a times b plus c. We pulled a, b, and c together into an addition. Problem here is that this is a non-local simplification. It can't be encapsulated in the method of a single object. You are back to square one, you need test and access methods for all the different subclasses.