BruceEckel argues for CeePlusPlus Templates, and in particular why JavaGenerics are bad, by assuming that LatentTypes are good (see http://mindview.net/WebLog/log-0051). This page argues against Latent Types (and thus indirectly against C++ Templates).
[[Actually, it argues against signature-typed genericity. On this page, for "latent typing" read "signature typing".]]
He provides the following C++ example:
class Dog { public: void talk() { } void reproduce() { } }; class Robot { public: void talk() { } void oilChange() { }};
template<class T> void speak(T speaker) { speaker.talk(); } int main() { Dog d; Robot r; speak(d); speak(r); }For anyone not experienced in basic C++, this code shows that the speak() procedure (which has been templatized) will try to use the talk() method of whatever object it is passed. At compile-time the speak() procedure will check that the object it is being used on has a talk() method. Contrast this with the slightly more complex Java version:
interface Speaks { void talk(); } class Dog implements Speaks { public void talk() { } public void reproduce() { } } class Robot implements Speaks { public void talk() { } public void oilChange() { } } class Communicate { public static void speak(Speaks speaker) { speaker.talk(); } } public class SimpleDogsAndRobots { public static void main(String[] args) { Dog d = new Dog(); Robot r = new Robot(); Communicate.speak(d); Communicate.speak(r); } }For anyone not experienced in basic Java, this code shows that the speak() method will use the talk() method of the object it is passed. At compile-time the speak() method will (indirectly) check that the object it is being used on has a talk() method by checking the object implements a Speaks interface.
Certainly the C++ code seems simpler, but without losing any safety - perhaps Bruce is right about LatentTypes?
The crux of the matter is that the C++ version requires that whenever the speak() procedure is used, the programmer needs to look at the speak() procedure's code to know that the passed object must implement a talk() method, and then the programmer must check the the passed object does indeed have such a method (otherwise an error will be generated). Contrast that against:
The Java version never requires the programmer to look at the speak() method's code, because the programmer can see from speak()'s parameters that it can only work on an object that implements a Speaks interface. Then the programmer must check that the passed object does indeed implement such an interface (otherwise an error will be generated).
This is the heart of the problem with LatentTypes: It requires that the programmer know exactly how any specific (templated) procedure has been implemented, and from that abstract what kind of requirements the procedure puts on to any parameters it is passed. This is a horrendous mistake which goes completely against information hiding.
The obvious counterargument: since the error occurs at compile time, this is a pretty benign problem -- if the programmer does not know that a particular method is required, she will get an error message saying that that method is required. The set of method signatures needed by a generic procedure in a system with latent-typed genericity will, in practice, be no more restrictive than in an interface-based system (it may be less restrictive), and so the argument on the basis of information hiding is simply wrong.
Worse still, if the (templated) procedure's implementation changes slightly, such that it now uses an additional method of a (parameter) object, all code that uses this procedure could break.
Yes. This is in fact the only problem. The code will fail to (re)compile; it will not break silently.
The only reason this might not happen is if the procedure contains usage comments which state all the methods which might be needed for all the objects it is passed!
Java's strict adherence to explicit types means that such a mess cannot happen, because the passed object type is always stated in the definition of the method (the equivalent of C++'s procedure in this example). So in summary, LatentTypes sound great, but they leave such much information unstated (and in fact unchecked) that (a) the programmer is required to spend a lot of extra effort when writing code, and (b) they also leave programs wide open failing as soon as someone changes a seemly trivial implementation detail.
Explicit types do indeed provide safety (which LatentTypes also provide), but they also provide explicit abstraction of the requirements of procedures & methods (which LatentTypes do not provide).
-- ChrisHandley
This appears to be comparing C++ templates to ordinary DynamicBinding in Java (as opposed to JavaGenerics) -- an apples to orange comparison. The Java design can be implemented in C++ (without templates) rather easily, as follows:
class Speaks { public: virtual void talk() = 0; }; // public virtual inheritance might be better, as it more accurately simulates // Java's interface implementation...but that's beyond the scope of this page. class Dog: public Speaks { public: void talk() { } void reproduce() { } }; class Robot: public Speaks { public: void talk() { } void oilChange() { } }; class Communicate { public: static void speak(Speaks &speaker) { speaker.talk(); } }; void main(int argv, const char **args) { Dog d; Robot r; Communicate::speak(d); Communicate::speak(r); }Like the Java version, DynamicBinding (evaluated at runtime) is used to select which subtype of Speaks to use; templates are nowhere to be found, and you have to "know" what methods are defined in Speaks.
But the non-template solutions (as well as the solution using JavaGenerics) have a problem, not found in the C++ version. Suppose you have a class that implements talk() but does not derive from Speaks. With C++ templates, you can use it in your Communicate class. With the other methods being discussed, you cannot -- the only way to get it to work is to go back and modify the class so it does implement Speaks. Which may or may not be possible/practical.
BjarneStroustrup and crew did consider adding BoundedPolymorphism to C++, and I think it would be a good thing to have (in addition to, not instead of, what we have nowadays). The main advantage with the non-template technique is if you use an incompatible type, you get an explicit error message to that effect. The C++ method will just as likely give you a rather obscure error message, "occurring" within the bowels of some header file.
You might read TheDesignAndEvolutionOfCpp for more info on why C++ is as it is. A highly recommended book.
Chris, I think you missed Bruce's point. The problem with Java generics is that they don't seem to add anything to Java. They require explicit declaration of interface, so there's no clear benefit over using interfaces without generics. His Java example was:
interface Speaks { void speak(); } public class Communicate { public <T extends Speaks> void speak(T speaker) { speaker.speak(); } }What advantage do generics provide when we can already do this:
interface Speaks { void speak(); } public class CommunicateSimply { public void speak(Speaks speaker) { speaker.speak(); } }-- EricHodges
In this example, there is no benefit. OTOH, for writing homogeneous collection classes, there is lots of benefit; as we can make lists of Apples that can only contain Apples; currently with Java we only can make a list of Objects; and someone can just as easily put a Lemon or an Orange or a SecurityManager or a NullPointerException, etc. into such a list. As a result, the user of such a list is required to downcast to Apple everywhere.
With the generic mechanism; the compiler essentially "writes" for you a specialized version of List, which can accept only apples. Instead of
class Apple { private final String type_; public Apple (String type) { type_ = type; } String get_type () { return type_; } } public class SimpleList { private Object elem_; private SimpleList? next_; public void insert (Object insertMe) { /* implementation snipped */ } public Object car () { return elem_; } // make SmugLispWeenies happy // lots of other stuff snipped } SimpleList list; list.insert (new Apple("Red Delicious")); list.insert (new Apple("Granny Smith")); list.insert (new Object()); // whoops, but legal Apple a = (Apple) list.car(); System.out.writeln ("My apple is a ", a.get_type(),"\n");one can write
public <T extends Object> class SimpleList { private T elem_; private SimpleList<T> next_; public void insert (T insertMe) { /* implementation snipped */ } public T car (void) { return elem_; } // make SmugLispWeenies happy // lots of other stuff snipped }When the compiler expands a definition of SimpleList<String>, for example, the resulting generated code looks something like this:
public class SimpleList$String { private String elem_; private SimpleList$String next_; public void insert (String insertMe) { /* implementation snipped */ } public String car (void) { return elem_; } // make SmugLispWeenies happy // lots of other stuff snipped }[[Actually, the Java compiler does not expand code for each instantiation. It rewrites it to essentially the same code as the example using Object above.]]
// The resulting client code looks like this.
SimpleList<Apple> list; list.insert (new Apple("Red Delicious")); list.insert (new Apple("Granny Smith")); list.insert (new Object()); // Compiler will flag as error Apple a = list.car(); // Downcast no longer necessary System.out.writeln ("My apple is a ", a.get_type(),"\n");And so forth for any other objects we want a list of. No chance of inserting a ScreenWidget or a FooBar into your list of strings; and no need to downcast the result of the car() method in order to use it in the manner expected.
JavaGenerics may not have as many uses as C++ templates; but to say that they aren't useful is simply mistaken. They will clean up the collection classes immensely.
Then the name is misleading. GenericProgramming means much more than what Java Generics provide. Homogeneous collections are nice, but they aren't GenericProgramming. Perhaps Sun should have called it Collections 2.0 or something instead.
And of course Java Generics solve much more than Java Collections.
Of course. Looking at the tutorial we see that they solve... um... well, perhaps "much more" is overly enthusiastic. We can abstract types, but if we do want to call methods on instances of those types we have to treat them as Objects or specify an interface for them.
That's the spirit!!! Of course, the designers seem to agree with the creator of this page (and I tend to favor their point of view as far as I can tell the pro and cons arguments) that LatentTypesSmell, so they force you to make contracts first class constructs, like interfaces. See also the discussion below. They're not as perfect as ML modules and functors or Haskell type classes, but hey, we're talking Java here, and I think they did a fine job to keep it within the level of accessibility of the average Java programmers.
I disagree that LatentTypesSmell. The argument at the top of this page seems to be that latent types will force the programmer to rely on the compiler for detecting type errors. I don't have a problem with that. I already rely on the compiler for detecting type errors.
Not exactly that. There are two arguments against latent types:
The problem with BruceEckel's point is that he just doesn't get it, yet he writes a totally misguided and misinformed rant. Of course, Java Generics were not designed to solve examples like the above, that's just a StrawMan. Any decent programmers after a cursory look at the generics tutorial http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf will have an idea as of what generics are good for.
Yet Chris is correct that latent typing has its set of problems, and he is correct to point out that Bruce's plea for latent typing in Java generics is misguided as it was a conscious design decision on the part of Java designers not to include latent typing. And again, they were not trying to solve the design problems that Bruce thinks Java Generics should solve, but an entirely different set of problems.
That said, within the realm of C++, latent typing of templates is quite useful (for other problems besides implementing container classes). To claim that latent types smell is stretching it a bit. (Besides, the "smell" tag is usually used on Wiki for questionable programming practices, not questionable language features). Oh yeah? What about LanguageSmell?
Any argument that C++ should be more like Java, or vice versa, misses the point. If you want Java, use Java. If you want C++, use C++. And if you want Smalltalk, use that instead.
Well, I'm not sure. It can be argued that latent typing is a smell, after all. It is an abdication from modularity principles and design by contract principles. Whatever problems latent typing solves are solve more elegantly and without giving up on good SoftwareEngineering practices in languages like ML or Haskell, so latent typing is not a necessity but a convenience for C++ implementors and an inconvenience to the software engineers making use of it.
The most problematic thing is that interfaces between modules should be made explicit, and they should be first class objects.
[[Interfaces between modules are not first-class objects in C++, or in ML or Haskell or most other StaticallyTyped languages.]]
When you write a collection like a C++ STL collection, you expose a contract between your implementation and its users. The code will work only if the users will provide to you classes matching that contract. Or in C++ the contract for generics is not something that is made explicit, but something that is derived from the totality of methods/operators invoked on the generic objects in the implementing code. This breaks the elementary principle that you should separate the interface from implementation. Somebody in need to use your templated code will get the message that his class doesn't conform to a contract that he doesn't see in the header from the compiler, and most of the times, in the most obscure way possible, referring to a line in the implementation code.
Of course this problem can be remedied by documentation, for example the generic algorithm stable_sort uses an iterator and that iterator should conform to a contract known as RandomAccessIterator? (and the more complex version uses also the concept of WeakOrdering?. Now the designer of stable_sort cannot specify that contract in a first class construct of the C++ language, he has to resort to documentation: http://www.sgi.com/tech/stl/RandomAccessIterator.html. This is where LatentTypesSmell as they abdicate from basic ideas about modularity and good software engineering.
[[So a reasonable conclusion is that languages should default to signature-typed genericity (what is called "latent-typed" on this page), but allow constrained genericity.]]
From above: No chance of inserting a ScreenWidget? or a FooBar into your list of strings; and no need to downcast the result of the car() method in order to use it in the manner expected.
Except that Java's List<T> derives publicly from (non-generic) List, so you in fact *can* put a ScreenWidget? into your List<String>. As a result, "generic" containers in Java really are just sugar.
Of course it's an implementation problem, not a fundamental one; libraries in some other languages -- C#, for example -- don't have that loophole.
This doesn't work in Java with generic lists... List<T> does not at all derive from non-generic lists, and the add function will not work on mis-matched types.