
Daryoush Mehrtash wrote:
I have had an unresolved issue on my stack of Haskell vs Java that I wonder if your observation explains.
If you notice java generics has all sort of gotchas (e.g. http://www.ibm.com/developerworks/java/library/j-jtp01255.html). I somehow don't see this discussion in Haskell. I wonder if haskell's model of letting the caller determine the result type has advantage in that you don't have all the complexity you would have if you let the API determine their types.
That "gotcha" about generics, is really a gotcha about arrays. If you replace List<> with [] in their example it'll compile just fine, though it can never work. The runtime happens to catch this particular error as an ArrayStoreException, but there are more convoluted examples that let you break the type system in all sorts of disturbing ways. As I said before, co-/contravariance is a Hard Concept(tm) where intuitions lie. I think the big difference is that Haskell (similar to some non-Java OO languages) separates the notion of a value from the notion of associated functions. In Haskell we expect that the result of a function, say, is a value with an actual type-- the actual type needed by the caller in fact, perhaps constructed by a type-class dictionary passed down by the caller. Whereas in Java the result of a function is some stateful bundle of functions-- and whatever the type is, the callee returns the functions that can be used on it. This is highlighted in Haskell by type signatures like |Foo a => a|, sure the type of our result may have a bundle of associated functions, but we know that the result is really a value of type |a|, polymorphically if need be. For simple object-like types that may not seem so different, but for container types the difference becomes readily apparent. In Java an interface needs to cement the collection and so you often see interfaces with different versions of the same function only offering/accepting different collections. There are some abstract classes or interfaces that try to expand on this to make things more polymorphic, but this perspective still can't escape certain problems... For example, the fragile base class problem. One of my gripes about Java is that for some unfathomable reason, Iterators are not Iterable. More particularly, for iterators from library collections, I often cannot make them so[1]. In Haskell, since associated functions like iterator() are not part of the value but instead exist ethereally, we'd be free to give an instance for all Iterators making them Iterable. This is the sort of problem that Pythonistas and Rubyists work around with monkey patching. Another example is, say we have an object that constructs a priority queue internally, and ultimately hands it off to the caller; this class doesn't actually care about the pqueue itself, though it doesn't treat it as a black box either. If someone comes up with a more efficient implementation of pqueues they can't just require it (via polymorphism) from that class, instead they need to go in and change the class that doesn't care[2]. In a sense, Haskell embraces the dread spectre of multiple inheritance. There are inheritance trees of type-class instances, but the association of these functions to datatypes is flat, and datatypes can always opt into additional type-classes. The diamond problem of C++ multiple inheritance goes away since values never inherit additional fields, however we do still have diamond problems of our own which OverlappingInstances and IncoherentInstances try to work around. Type-classes do specify types in the API, they can even specify types in ways that Java interfaces cannot (e.g. |a -> a -> b| [3]). But the way they specify types, functional polymorphism vs inheritance, are radically different. [1] Cf. Vector which uses an anonymous local class. In order to have Vectors that do return iterable iterators, we need to subclass Vector and override iterator() with an almost identical declaration that adds a method: public Iterator<E> iterator() { return this; } [2] Potentially by subclassing, assuming the class doesn't have too many interdependencies. Or by using some defunctionalization pattern to mimic caller-directed polymorphism, especially if we want to reuse the same factory for multiple callers for whom different pqueues are more efficient. [3] Java has only one top type: Object, and so it can't ensure this constraint. In some cases generics can be bent to fill this gap, but in others it's much harder, e.g. |class Foo a where foo :: Int -> a|. OO-Languages like SmallTalk can better capture some of these because they treat classes as objects too, but there are still differences. -- Live well, ~wren