The Programmers Guide To Kotlin - Generics |
Written by Mike James | ||||||
Monday, 04 June 2018 | ||||||
Page 2 of 2
Supplying Generic ActionsSo how can generics implement anything useful if you cannot apply any type specific actions to generic entities? One very general answer is to use implementations of a generic function type that implements the operation you require on particular types. For example, you can't use a+b when the types are unknown but you can rewrite a custom add function as:
Notice now that the third parameter is a function that takes two parameters of type T and returns a T. This is a generic formulation of a function that takes two parameters of the same type and applies a function to them that that is also passed as a parameter. The function that is passed in its turn takes two parameters of the specified type and returns a result of the same type. All of this is fine as no knowledge of the type T is used in this – it works for any type including Any. Now lets try adding two Ints:
The first two parameters are fine as they are Ints but the third isn't defined and it needs to be a function that accepts two Ints and returns an Int. This is easy to define:
This is a lambda, see Chapter 11. The lambda is defined within the curly brackets and it is a function of the correct type i.e. (T,T)→T where T is Int. Now everything works and:
returns three. Notice that the function you pass is not a generic – it has a definite type even at compile time. The downside is that you need to define an auxiliary function for each of the types that you actually want to process. You can make this look more like a fully generic solution by defining a typealias:
Once again the sumInt is fully defined at compile time including the type of its parameters. Generic ConstraintsThe problem is that when we use a type parameter like T it can be any type. This is the advantage of generics but it means that, if we want to use any operations on a particular type we have to implement specific functions that work with the type. The reason we can't call any methods on a generic type is that the compiler has no idea what the type is at runtime. We can relax this by applying generic constraints to the type parameter which limits what sort of types are allowable. Knowing that the type parameter must be a particular type or a subtype of that type we can allow methods to be used secure in the knowledge that the methods will exist at runtime. The only constraints that Kotlin provides are generally called "upper bounds". You can follow the type parameter with a type specifier. This gives the base class for the set of types that the type parameter can be. In other words the type parameter has to be either the specified base class or a class derived from it. This allows the compiler to infer that an object described by the type parameter can have any of the methods of the base class and so these can be used in the generic. For example:
defines a function that accepts an object of type MyClassA as a parameter or anything derived from it. Notice that this is the same as
as a derived class can be used in place of a base class. Also notice that constraints have the same problem as using a base class as a type. If the base class doesn't implement the methods you want to use then you can't make use of them. For example, you still can't implement a generic add function because Number doesn't define an addition function. That is
doesn't work because Number doesn't define an addition operation. Even with constraints you are still at the mercy of the way the class hierarchy is constructed in what you can easily implement as a generic. As well as a single base class constraint you can also specify multiple constraints using where. For example:
In this case T has to be derived from the Number class and also implement the Comparable interface. All of the usual number classes satisfy this constraint. With the constraint in place we can use the greater than operator to return the maximum. Also notice that Comparable is itself a generic interface and this form of constraint would be difficult to implement in any other way. This is more powerful but notice that we still can't implement a generic sum function because there isn't a Summable interface. Each of the Numeric types implement their own plus function and it would seem reasonable that any Number type would have a plus function but it isn't defined in Number or in a Summable interface. This is a completely general problem. If you want to write a generic that works with all of the derived classes of an upper bound then the class that forms the upper bound has to have all of the methods you want to use.
Co and Contravariancefinal version in book Co & Contra-variant Genericsfinal version in book Controlling Variance – In and Outfinal version in book Type Projectionsfinal version in book The * Projectionfinal version in book Summary(Italicized text refers to material not included in this extract)
This article is an extract from: Programmer's Guide To Kotlin Third Edition
You can buy it from: Amazon Contents
<ASIN:B0D8H4N8SK> To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.
Comments
or email your comment to: comments@i-programmer.info |
||||||
Last Updated ( Monday, 04 June 2018 ) |