Tuesday, May 12, 2009

Dynamic dispatch is just higher-order programming

Wow, this is pretty terrible coming from a Haskell programmer (via HN):

Dynamic dispatch is a scary programming technique. When you call a virtual method, you never know what might happen. This makes is [sic.] difficult to reason about such code, and code that is hard to reason about is hard to maintain.

This is true in the exact same sense that when you implement a higher-order function, "you never know what might happen". Here is a naive implementation of map in Standard ML:

fun map f [] = []
  | map f (x::xs) = (f x) :: (map f xs)

f could be bound to anything! OMG WTF BBQ!

Likewise here is a Java implementation of printList:

void printList(List aList) {
    for (Object o : aList)
        System.out.println(o.toString());
}

o.toString() could dispatch to anything! OMG WTF BBQ!

Well hold up man, let's try that in ML:

fun printList _ [] = ()
  | printList toString (x :: xs) =
    (print ((toString x) ^ "\n");
     printList toString xs);

Oh that's so much better isn't it. Or not. Basically, you will note that we had to type toString three times instead of once to achieve roughly the same effect.* Note also that this function only works over homogeneous lists; if you want a heterogeneous list, you'll have to define a union type and pack/unpack it yourself.

Object-oriented programming is a form of higher-order programming wherein related data and operations are tightly bound to each other, thus freeing you from the pain of having to wrap them up yourself, thus making it exceptionally convenient to pass data and functions together. Or, in other words, OO programming is just a functional programming idiom with lots of very convenient syntactic and semantic sugar on top**.

Now, the OO community has plenty of bad code and bad guruism bouncing around. Maybe there's somewhat less of that in the functional world. But IMO that's largely explainable by the fact that there are vastly more working OO programmers and vastly more OO code modules than functional equivalents. If the Haskell or ML community were as big as the Java community, there would be just as much terrible higher-order function spaghetti as there is terrible inheritance spaghetti today.***


* Q: Wouldn't the version using Haskell and type classes be as compact as the OO version? A: Maybe, but you'll still have to restrict the function's domain to homogeneous sequences of StringConvertable instance values. Once I hand you a heterogeneous list, you're back in the land of passing toString and packing unions. The only way to do this right is to introduce existential pack/unpack into your language, which basically amounts to introducing a slightly crippled object system with additional syntactic overhead.

** In particular, making super sends and self sends work correctly without OO sugar gets messy. OTOH, conversely, the object-oriented programming languages in common use make certain other kinds of functional programming idioms somewhat baroque — for example, Java's lack of a compact syntax for anonymous functions is a huge pile of Lose — but that's a discussion for another day.

*** In fact, I strongly suspect that with a fair number of kids today thinking that Haskell is the new hotness, that community's in for a rude awakening as they realize that Haskell's going to require style guides and "Haskell annoyances" books and assorted baroque frameworks in order to allow programmers of average ability to assemble software of similar complexity to that commonly assembled in Java today.

3 comments:

  1. Oh man, I think you are right, maybe it's kinda too abstract.. after reading it, the only thing I can remember is that they are both OMG WTF BBQ!

    ReplyDelete
  2. Just a minor point: you should not confuse OO programming and inheritance. The first one is about packing data and code together, the second one about code reusability. Most so called OO languages offers both, but it's no more accurate than saying lazyness is part of functional programming.

    ReplyDelete
  3. An encapsulated unit of code and data is an abstract data type (ADT). Language support for ADTs is indeed separable from inheritance. However, the languages supporting encapsulation but not inheritance have not traditionally been called object-oriented: for example, CLU, old dialects of Pascal, and Go are not usually thought of as OO. In fact I'm pretty sure Rob Pike would bristle at the suggestion that Go is OO.

    SIMULA-67 had both inheritance and encapsulation. (And an Actor-like concurrency model, incidentally.) It was the first OO programming language. I don't think there's any coherent account of the development of OO in which inheritance is not integral to the definition.

    ReplyDelete