[Haskell-cafe] Re: What I wish someone had told me...

wren ng thornton wren at freegeek.org
Wed Oct 15 18:32:12 EDT 2008


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


More information about the Haskell-Cafe mailing list