In this session, you'll learn about a particular way to do context abstractions and to turn types into values, which is called type classes. In fact, you've seen the pattern of type classes already. Here it is again. We have a trait like ordering and the compare method, and then we have a comparing object with given instances for particular type instances of that trait. We have a given instance for ordering of int, and we have a given instance for ordering of string, and both of these instances define the compare method as is appropriate for that human type here. If you have a pattern like that, we say ordering is a type class. In Scala, a type class is made up of a generic trait, so a trait with a type parameter, and given instances for type instances of that trait. In the ordering example, we have the given instances for ordering of Int and ordering of String and the type class is ordering. Type classes provide yet another form of polymorphism, being able to come in many forms, and that form is sometimes also called ad-hoc polymorphism. To see what I mean, look at the sort method. It can be called with lists containing elements of any type A for which there is a given instance of type ordering of A. Sort can be called on arguments of many types, and its functioning is different depending on what type it is because it's functioning will depend on the given instance for the actual type A that you see here. At compilation time, the compiler resolves the specific Ordering implementation that matches the type of list elements. Resolving simply means providing an argument in a using clause that matches this Ordering context bound. As an exercise, let's implement an instance of the Ordering type class for the rational type. I'm going to simplify a lot, for now, the class rational will simply be a case class with a numerator and denominator and no additional methods, we don't need any for this particular behavior. For the Ordering type class, we need to implement a compare operation, and here's a reminder of what less than is on rational numbers Q and R. What we do is we simply multiply the numerator of one side with a denominator of the other. That gives this here. Do the same thing on the right-hand side, so it's numerator gets multiplied with the denominator on the left-hand side, and then we compare those two products. Let's do this in the worksheet. We have our class rationale here, our trait Ordering here, and we have a given instance, let's call it rational ordering of ordering of rationale, which has a compare method and we have to implement the right-hand side of that method. According to the formula, we define two helper values, x and the normalized this numerator of x times the denominator of y. Val yn would be then the numerator of y times the denominator of x. Then we have the usual comparisons, so if xn is less than yn, then we return minus one else-if xn greater yn, we return one else, we return zero. You might ask, why haven't written simply xn minus yn? Because it gives you arguments of the same sign as this expression here. The answer is this one here is placed better with integer overflow. If I compare two numbers that differ by a very large amount, then that subtraction might overflow, whereas here the comparison will still give you the good result. That's why it's written that way. It's worth noting that we were able to implement the ordering rational instance without changing the rational class definition. That means the type classes support retroactive extension. We can extend a data type with new operations such as compare, in this case, without changing the original definition of the data type. In this example, we've added the capability of comparing rational numbers without changing the class rational. That's one of the advantages of using type classes over using regular classes, that you can do that. Here's another advantage, you can do conditional instances. The question arises, how do we define an ordering instance for lists? Well, it depends what lists of what element, because we can define an ordering on a list only if the elements have an ordering. That leads us to a definition like this one here. We have a list ordering with some type parameter A and that is an implementation of an ordering of list of A. But we need an ordering of A as well, which we can express here by adding a using parameter clause to the given name here that you see over there. Then we need to define the compare method, here it is. The compare takes two lists of As and it does a pattern match on the pair of Xs and Ys. If they're both nil, then they're equal. If the first one is nil and the second is not nil, then the first one is smaller. If the second one is nil and the first one is not, then the first one is smaller. If they are both non-nils, they are both constants with heads X and Y, and tails Xs1, Ys1, then we compare the heads, and for that we need our type class here, our ord. We use ord to compare X and Y. That gives a result C and then if that result is zero, then we compare furthermore the list Xs1 and Ys1. We compare the tails. If the result is different from zero, then we have our result and that's what we return. One new syntax that we've seen is that a given instance list ordering can take type parameters and implicit parameters. You could also leave out the name, in which case you would just have a given instance without a name and type parameters, given parameters, that would work as well. We observe that given instances such as list ordering that take implicit parameters are conditional, that means an ordering for lists with elements of type T exists only if there is an ordering for T. Because otherwise the compiler couldn't synthesize an actual argument for the using clause in list ordering. This sort of conditional behavior is really best implemented with type classes. Normal subtyping and inheritance cannot express this. A class either inherits a trait or it doesn't, but you can't make a class inherit a trait depending on a condition of its parameter types, that's not possible. But with type classes it is. Things get interesting if we look at how instances of conditional implicit parameters are resolved. In fact, the resolution proceeds recursively. That means a given instance for the auto type list in this case, is constructed first and then its implicit parameters are filled in in turn. For instance, let's say we have the sort method which takes an Ordering A type class instance, and we want now to sort a list of list of int. That's the original Core. Type inference gives us a list of instance the element type A that we want to sort. Now we need an ordering instance for list of int. The ordering instance we find is list ordering, so that's what we pass. List ordering itself needs a ordering instance for the element type, which is int in this case, so this expression in turn is expanded to this one here, using list ordering that takes ordering of int parameter. We started with this and we got this. That is essentially the work of the compiler that added the missing pieces. It's also of worth noting that each of the intermediate result is actually a valid scholar program, so you can write this of course, you can also pass the type parameter explicitly, of course, but you could also add just list ordering essentially to help the compiler along. It says, well, I need a list ordering. But now you can figure out yourself what argument to pass to the list ordering instance here. You could either write this last thing or leave it out in turn. Here's another exercise. Let's implement an instance of the ordering type class for pairs of type A and B, where A and B must have ordering instances defined on them. That's useful for examples like this one here. Let's consider a program for managing an address book. We would like to sort the addresses by zip codes first and then by street name. So if two addresses with different zip codes are ordered according to their zip code, otherwise, when the zip codes are the same, the addresses are ordered by street name, so that would lead to the setup that you see here. We have a type address which is a pair of int and string. We have a list of addresses, and we want to sort that list. What is the given instance that we can pass to that sort function? Here's a solution. We have a given instance, let's call it pair ordering of ordering of A and B. Here, A and B are type parameters so that works for any pairs of types A and B, but they must both have an ordering instance, so it must have an ordering instance for A and one for B. We give them names, orda for the one for a and ordb for the other one. Now we have to define a compare method for pairs, so we compare method takes two elements, X and Y, both of type pair of AB. What we do is we compare first the first half of these pairs so that's X._1 and Y._1, that gives us a result. If that result is different from 0, then that's what we return, and otherwise, if the result is 0, we go on and compare the second half of the two pairs. For the comparisons, we use orda in the first case because that's the comparison we need to use for the a parameter and we use ordb in the second case. We've seen that ordering has a compare method, but typically, when we use an ordering, we don't really want to use compare, we want to use less than, or greater than, or methods like that. That's possible because like any trade, a type class trade can define extension methods. For instance, the ordering trade would usually contain comparison methods like this so that's the compare method which is abstract, and then we have extension methods less than, less than or equal, greater than, greater than or equal, which all use compare to produce their results. That's handy because now we can use a natural less than operators and the others for comparisons. Here's an example in the merge method of MergeSort, so merge works for any type T that has an ordering so that's an implicit parameter ordering of T that we assume here. As usual, pattern match which says, well, if one of them is nil, then take the other, and if they're both non-nil, then we compare X and Y. That uses a comparison operation less than, which is the less in ordering so that less is really the less that we have here defined as an extension method in the ordering class. Now you might wonder how does the compiler know that this less than means the ordering less than? It's not defined in the environment, it's not imported. In fact, we don't even have a name. We could import it from ordering. It's just the context bounded isn't a name parameter. Now the trick is that there's an additional rule which says that if we have a type class instance such as ordering in scope, then its extension methods are eligible for the type that is the argument of the type class. We have ordering T in scope and that means it's extension methods are defined on values of type T. That's what makes it work without any additional boilerplate. To summarize, type classes provide a new systematic way to turn types into values. Unlike class extensions, type classes can be defined at anytime without changing existing code, and they can be conditional. That makes them a lot more flexible and powerful than plain class extensions that we have seen before. In Scala, type classes are constructed from parameterized traits, that's a type class, and given instances for that trait. Type classes give rise to a new kind of polymorphism, which is sometimes called ad-hoc polymorphism. That means that the type TC[A] has different implementations for different types A.