Generic Vs Object Oriented Programming

In an interesting interview at www.stlport.org Alexander Stepanov says: "I think that object orientedness is almost as much of a hoax as Artificial Intelligence." See Object Orientation Isa Hoax.


Stepanov is IMO needlessly hostile in his opinions towards OO. I'll try to describe the differences between generics and objects, as I've felt them, briefly in here. The more aggressive discussion follows further below...

Generics cannot:

use behaviorally heterogeneous collections (for example, list of different implementations of common ABC)

more generally, forget anything about the type of a datum

Objects cannot (in a language which uses Static Typing; Dynamic Typing doesn't have these restrictions):

allow routines on compound types which make no assumptions about the component types

more generally, define any polymorphic behavior without first defining an interface.

Workarounds for overcoming the limitations exist in both directions. Java's downcasts are one such mechanism, and you can put objects or something providing equivalent power (functions / collections of functions) into a generic data structure.

Note that there are two kinds of generics:

compile-time generics, familiar from C++, which are much akin to macros for code synthesis.

runtime generics, familiar from the Sml Language (Objective Caml) and other Functional Programming Languages, which are no use without Higher Order Functions.

Objective Caml and Haskell Language both have both of the above, the former in the form of modules, the latter in the form of existential classes.


Later in the interview there is the following:

Q: This mean a radical change of mind from both imperative and OO thinking. What are the benefits, and the drawbacks, of this paradigm compared to the "standard" OO programming of Small Talk or, say, Java?

A: My approach works, theirs does not work. Try to implement a simple thing in the object oriented way, say, max. I do not know how it can be done. Using generic programming I can write:

template <class S''''''trictWeakOrdered> inline S''''''trictWeakOrdered& max(S''''''trictWeakOrdered& x, S''''''trictWeakOrdered& y) { return x < y ? y : x; }

and

template <class S''''''trictWeakOrdered> inline const S''''''trictWeakOrdered& max(const S''''''trictWeakOrdered& x, const S''''''trictWeakOrdered& y) { return x < y ? y : x; }

Yeah, that's pretty easy. In Smalltalk it's much harder. Implement this method on object:

max:
anObject

^self > anObject ifTrue: [self] ifFalse: [anObject]

Much more difficult.

'

(you do need both & and const &). And then I define what strict weak ordered means. Try doing it in Java. You can't write a generic max() in Java that takes two arguments of some type and has a return value of that same type. Inheritance and interfaces don't help. And if they cannot implement max or swap or linear search, what chances do they have to implement really complex stuff? These are my litmus tests: if a language allows me to implement max and swap and linear search generically - then it has some potential.

Can someone who knows explain what he is getting at? Is GenericProgramming that much superior?


The name StrictWeakOrdered is just a placeholder here. There doesn't need to be a class named StrictWeakOrdered anywhere in the system. Including a header file containing this code in a source file allows the programmer to invoke max(x, y) anywhere therein, no matter what object types x and y are. However, the statement will fail to compile iff the statement x < y would cause a compile error. Thus, the only restriction on x and y is that operator<() exists, either as a free function taking x and y as arguments, or as a member function for the class of x taking y as argument. If x and y are native types, this is automatic.

In Java, on the other hand, there are no generic functions. Some people consider this good, of course. So, the appropriate way to do this is to have x and y be members of a Comparable class (or to provide an extra Comparator), and declare a utility class:

public final class Ordering { public final static Comparable max(Comparable x, Comparable y) { return (x.compareTo(y) < 0) ? y : x; }

// Optional: public final static Object max(Object x, Object y, Comparator cmp) { return (cmp.compareTo(x, y) < 0) ? y : x; }

// Min, median, etc. }

You can then invoke Ordering.max(x,y) as appropriate. Note that Alexander Stepanov could have provided a three-argument version too.

The question is, which is appropriate to your needs?

It's difficult to see what need there would be for the approach in this examples while using language with dynamic typing. It's a few years since I worked in C++, but back then we used C++ templates to fake up many things that Lisp and Smalltalk people take for granted (closures, strong collections, functoids etc). Apart from that, the advantage of C++ templates, which are only one form of genericity, is a particular form of late binding. It'd be interesting to know what Stepanov would have to say about other forms of genericity.

It's not clear to me quite what he's getting at with this example. Certainly, an OO (or any other kind of) language that has static typing can benefit from some explicit mechanism for genericity. Languages with dynamic typing have other mechanisms for doing the same things.

As for the idea of expressing your algorithms in as abstract a fashion as possible well, probably You Arent Gonna Need It, plus, things like Beck's Template Method do something very similar. -- Keith Braithwaite

Java is pretty much screwed without the generics introduced in 1.5. However, for a language like Small Talk or Lisp you are exactly right. There is no need for templates in these languages. In fact Generative Programming (and unfortunate name choice that is too close to Generative Communication) talks about this at length. A language with a dynamic type system does not need templates. The problem with Java is that it really rode the fence and has neither a static type system nor is it truly dynamic. As a result, it requires even more type casting than a language like C or Cee Plus Plus. For this reason, it really requires templates. However, one should not confuse genericity with Generic Programming. -- Robert Di Falco


Stepanov, although clearly a very smart person, apparently never recovered from that bacterial infection that sparked off the development of the Standard Template Library. -- Andrew Queisser


The problem I have with both the C++ and Small Talk ways of doing things is that they don't explicitly specify the requirements for the arguments to max(). I'm very happy with Java's interfaces and would like to see a Java-like language that looks something like this:

interface WeaklyComparable { boolean operator<(ThisClass other); boolean operator>(ThisClass other); };

int implements WeaklyComparable; float implements WeaklyComparable;

where class T implements WeaklyComparable: boolean max(T a, T b) { if(a<b) { return a; } else { return b; } }

That is, the user of a class should be able to retroactively declare that the class implements an interface, provided that it already supports all the methods. (If not you might need to write an adapter class.)

Note also that max() doesn't use the greater-than operator but requires it anyway. This allows the implementation of max() to be more easily changed later.

(Also, built-in-types should be structs, like in C#.)

(Also, the type system needs to make sense unlike my example.)

The problem with this is that you'd need to explicitly recognize and assert that your type implements every single property/interface that you use on it. That means you'd have to keep going back to your original type and changing its definition. This is no better than declaring an object that implements many interfaces. The advantage of the Generic Programming approach is that the definition of the object implicitly specifies the ways it can be used. In other words, if it has a < operator, it must also have a strict-weak ordering. The Ruby example below is also nice; but it's fundamentally different from the C++ example in that the C++ version won't be used unless it can be compiled, i.e. unless the < operator has been defined. This allows one to do interesting recursion with templates. See Compile Time Generic Average Function In Cee Plus Plus. -- DavidKTurner


This code above looks almost exactly like Haskell typeclasses. Standard typeclasses are Eq for equality, Ord for ordering (requires that the type already be an instance of Eq), Num for numeric types, etc. It's easy to make new typeclasses also

this is some helpful demo info: www.syntaxpolice.org there's a simple typeclass in my Haskell tutorial for the impatient. I create a Char Exts class that has isVowel and isConsonant methods. www.scannedinavian.org

As for true generics in Haskell, there's much more cool stuff, check this out: www.scannedinavian.org In this demo, the XML typeclass is defined, and later how to turn different types into XML. composition is defined separately, you don't need a type specific visitor method for example.

Another step past this is Generic Haskell, which is much more powerful than this basic support of Generics. -- Shae Erisson


Ruby can do this, only nicer.

-Snippet (test.rb):

x,y=12,23.7

def max(x,y) raise ArgumentError unless x.respond_to?("<") return x<y ? y : x end

print "x=#{x}, y=#{y}, max(x,y)=#{max(x,y)}\n"

def max(x,y) unless y.kind_of?(Comparable) and x.kind_of?(Comparable) raise ArgumentError, "Arguments must be Comparable!", caller end return x<y ? y : x end

print "x=#{x}, y=#{y}, max(x,y)=#{max(x,y)}\n" x=proc { print "bla" } print "x=#{x}, y=#{y}, max(x,y)=#{max(x,y)}\n"

-Output:

>ruby test.rb test.rb:19: Arguments must be Comparable! (ArgumentError) x=12, y=23.7, max(x,y)=23.7 x=12, y=23.7, max(x,y)=23.7 >Exit code: 1

-- Peter Thoman (my first ever Wiki edit, hope this works)


Can someone who knows explain what he is getting at? Is Generic Programming that much superior?

I think this is a silly argument. No OO does not provide a mechanism for easily creating simple functions or programs. The intent is to manage large numbers of functions in complex programs. The value OO brings is scoping. It binds functions to their appropriate types. Note that the "generic" max() function fails when used with dissimilar data types or with types which do not have the > operator defined. -- Wayne Mack

Right, and I think the current discussion of generic-vs-OO doesn't address the "scoping" advantages of OO languages. Those advantages are relatively well understood and the discussion has moved on. What's being discussed is whether the interface of a class should be tied to its type. In Small Talk and other dynamically typed languages type and interface are not directly tied to each other but in C++ a regular (non-template) function can only accept arguments of a specific type or subtype. A template (generic) function can accept anything that supports the operations used within the function (max needs the < operator.) The main difference now is that interface mismatches are detected at compile time (C++) or runtime (Small Talk, etc.) It seems to me that the generic-vs-OO discussion is carried out mostly within the C++ community because for the dynamic typing crowd there is nothing to discuss. (I'm extrapolating here, being C++ myself.) -- Andrew Queisser


Wayne Mack identifies a problem when the max() function is called with the wrong sort of object. This problem has been addressed, by using Sets Of Requirements. There is a lot of information on this in The Boost Graph Library. -- John Fletcher


Bertrand Meyer gives an excellent summary of the debate in Object Oriented Software Construction; for a summary of his summary see Generics Vs Subtyping


The expressive difference between Generics and Object Orientation is in:

How easy it is to write a program.

How easy it is to read a program.

How easy it is to debug a program.

How easy it is to modify a program.

If you think Static Typing is better than Dynamic Typing because you always make mistakes on method parameters, then you will very easily fix that 5% of the typing mistakes. Most mistakes are not typing mistakes though, so they can't be caught by Static Typing.

I personally think both have their points, but you are oversimplifying. For one thing, there are formal approaches in which all computation can be reduced to a matter of types, so you can't just wave your hands and say that mistakes in types are "5%" of all errors. In truth, you (and the rest of us, too) don't know the percentage in general, even if 5% happens to be true of your own code.

It is widely believed that there is excellent evidence that it is advantageous to catch as many errors as possible as early as possible, e.g. at compile time rather than run time, so even if it were only 5%, there might be a good argument.

Pure functional languages such as Haskell raise static typing to a high art, and accomplish vastly more at compile time than is traditional in languages like C++ and Java.

Writing Generics doesn't seem simpler than writing Object Oriented programs.

You seem to be talking only about C++, and you are correct that generic programming with templates in C++ is difficult and kludgy, but this is a phenomenon confined to C++ (and possibly the forthcoming Java generics); it does not reflect on Generic Programming in all languages.

Consider the following template declarations:

typedef xstr::xstring< xstr::fixed_char_buf<64> > small_string;

template <size_t SIZE, class CharT=char, class Traits=std::char_traits<CharT> > class fixed_char_buf;

Contrary to what rookie developers may think, the above declarations are simple compared to real production code that uses templates extensively. And that's nothing compared to the compiler errors you will get and the difficulty for debugging it. And only to achieve a 5% speed increase that never materializes because you are always debugging your code.

Compare with:

template

_bi::bind_t<R0, Find_SZ R0 (Find_CCH *) (R1), typename _bi::list_av_1<R2>::type> Find(Find_SZ R0 (Find_CCH *f) (R1), R2 r2)

{

typedef Find_SZ R0 (Find_CCH *F) (R1); typedef typename _bi::list_av_1<R2>::type list_type; return _bi::bind_t<R0, F, list_type> (f, list_type(r2));

}

Also keep in mind that the code above has another disadvantage: whenever you declare new variables, there is the possibility of creating a new class at compile time, just because the parameter happens to be sightly different. The code is therefore massive and it takes too long to load.


See original on c2.com