Thoughts on Haskell and OOP

Haskell has been around in one form or another for nearly two decades now, yet has never been extended with explicit support for object- oriented programming. I've been thinking about why this is so. I've come to the conclusion that Haskell simply doesn't need any explicit OOP support -- algebraic datatypes, modules, lazy evaluation, and first-class functions give you everything you need to do the kinds of things you would use classes and inheritance for in OO languages. To see why this is so, let's think about what OOP is. At the most basic level we have what some call object-based programming. This amounts to support for data hiding and abstract data types. Haskell's module system handles this function quite well, without any need to introduce a special concept of "class" (in the OO sense) nor private vs. public class members. Object-oriented programming is then object-based programming plus class hierarchies and inheritance. Why are these useful? Properly used, OOP is all about interface inheritance, not implementation inheritance. (At least in C++, implementation inheritance -- inheriting the data members and method implementations of a base class -- usually leads to bad design, and is discouraged by the experts.) (For those more familiar with Python, "duck typing" is the analog of interface inheritance for a dynamically-typed language.) Interface inheritance allows you to write procedures that operate on the base-class interface, but can be applied to objects of any type derived from the base class. Can we do this in Haskell? Yes, we can. Let's consider the Haskell analog of an immutable C++ base class: struct Base { virtual ~Base(); virtual int foo() const; virtual int bar(int n) const; }; class derived :: public Base { ... data members ... public: derived(T_1 arg1, ..., T_k argk); ... implementations of the virtual functions ... }; Haskell has no direct analog of object classes and virtual methods, but you can use lazy evaluation and first-class functions to achieve the same result: data Base = Base { foo :: Int, bar :: Int -> Int } derived :: T_1 -> ... -> T_k -> Base derived a_1 ... a_k = Base { foo = ...; bar = bar } where bar n = ... The analog of a mutable C++ base class is a little bit more involved, but not much. Suppose that we change bar(int) to be a mutating method in the C++ Base class: virtual int bar(int n); The Haskell analog then changes to data Base = Base { foo :: Int, bar :: Int -> (Int, Base) } derived :: T_1 -> ... -> T_k -> Base derived a_1 ... a_k = Base { foo = ...; bar = bar } where bar n = (..., (derived a_1' ... a_k')) (Here a_1', ..., a_k' are k expressions involving n and a_1, ..., a_k.) What do the rest of you think? Is my analysis correct?

On 2009 Jan 1, at 19:28, Kevin Van Horn wrote:
Haskell has been around in one form or another for nearly two decades now, yet has never been extended with explicit support for object-oriented programming. I've
http://homepages.cwi.nl/~ralf/OOHaskell/ http://research.microsoft.com/en-us/um/people/simonpj/Papers/oo-haskell/inde... http://www.cs.chalmers.se/~nordland/ohaskell/ http://www.timber-lang.org/ -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH

On Thu, 2009-01-01 at 17:28 -0700, Kevin Van Horn wrote:
Haskell has been around in one form or another for nearly two decades now, yet has never been extended with explicit support for object-oriented programming.
Yes it has albeit in spun-off languages. See O'Haskell and Timber.
I've been thinking about why this is so. I've come to the conclusion that Haskell simply doesn't need any explicit OOP support -- algebraic datatypes, modules, lazy evaluation, and first-class functions give you everything you need to do the kinds of things you would use classes and inheritance for in OO languages.
Certainly not, though (particularly with existential types) it does cover a lot of what people -actually- do with OOP. It is possible to -encode- OOP into Haskell, but it is very rare to see code that uses/does these encodings (for good reason.) OOHaskell includes several: http://homepages.cwi.nl/~ralf/OOHaskell/
To see why this is so, let's think about what OOP is. At the most basic level we have what some call object-based programming. This amounts to support for data hiding and abstract data types. Haskell's module system handles this function quite well, without any need to introduce a special concept of "class" (in the OO sense) nor private vs. public class members.
Object-oriented programming is then object-based programming plus class hierarchies and inheritance. Why are these useful?
I don't think most OO programmers would agree with either of these two paragraphs. I don't. Also there are OO approaches that don't use classes or inheritance.
Properly used, OOP is all about interface inheritance, not implementation inheritance. (At least in C++, implementation inheritance -- inheriting the data members and method implementations of a base class -- usually leads to bad design, and is discouraged by the experts.) (For those more familiar with Python, "duck typing" is the analog of interface inheritance for a dynamically-typed language.) Interface inheritance allows you to write procedures that operate on the base-class interface, but can be applied to objects of any type derived from the base class. Can we do this in Haskell?
While I agree that implementation inheritance does usually lead to poor design and should usually be avoided, I don't agree that it is "improper" and I do think many would consider it a crucial feature of OO.
Yes, we can. Let's consider the Haskell analog of an immutable C++ base class:
struct Base { virtual ~Base(); virtual int foo() const; virtual int bar(int n) const; };
class derived :: public Base { ... data members ... public: derived(T_1 arg1, ..., T_k argk); ... implementations of the virtual functions ... };
Haskell has no direct analog of object classes and virtual methods, but you can use lazy evaluation and first-class functions to achieve the same result:
data Base = Base { foo :: Int, bar :: Int -> Int }
derived :: T_1 -> ... -> T_k -> Base derived a_1 ... a_k = Base { foo = ...; bar = bar } where bar n = ...
The analog of a mutable C++ base class is a little bit more involved, but not much. Suppose that we change bar(int) to be a mutating method in the C++ Base class:
virtual int bar(int n);
The Haskell analog then changes to
data Base = Base { foo :: Int, bar :: Int -> (Int, Base) }
derived :: T_1 -> ... -> T_k -> Base derived a_1 ... a_k = Base { foo = ...; bar = bar } where bar n = (..., (derived a_1' ... a_k'))
(Here a_1', ..., a_k' are k expressions involving n and a_1, ..., a_k.)
There are a lot more issues than your example illustrates. That said, encodings like this one can often suffice. Luca Cardelli and co. did a lot of work on this in the '90s. See http://lucacardelli.name/Papers/ObjectEncodings.ps for an overview of much of it, though there is later work that resolves some of the issues in the conclusion. Ultimately, I'd say most Haskell programmers simply don't "do the kinds of things you'd classes and inheritance for." Occasionally techniques that could be viewed as (simple) object encodings are used, though rarely is that perspective actually taken. Other times other techniques are used to achieve similar ends. Most of the time, though, Haskell code is written in a functional style that is simply not extensible in the ways that OO code is.

"Kevin" == Kevin Van Horn
writes:
Kevin> What do the rest of you think? Is my analysis correct? No, because ... Kevin> Properly used, OOP is all about interface inheritance, not Kevin> implementation inheritance. (At least in C++, Kevin> implementation inheritance -- inheriting the data members Kevin> and method implementations of a base class -- usually leads Kevin> to bad design, and is discouraged by the experts.) But C++ isn't OOP. It's a bodge where OO stuff is added on top of C. Naturally, this doesn't work out well. But that isn't necessarily why C++ doesn't vdo implementation inheritance well. Properly used, OOP is all about interface inheritance and implementation inheritance, with contracts used to ensure semantically correct behaviour. See Eiffel. -- Colin Adams Preston Lancashire

I saw a blog entry once (sorry, google can't seem to find it now) proposing the idea that OOP really is just language support for CPS (continuation-passing-style). Does anyone have a link to this? Drew Kevin Van Horn wrote:
Haskell has been around in one form or another for nearly two decades now, yet has never been extended with explicit support for object-oriented programming. I've been thinking about why this is so. I've come to the conclusion that Haskell simply doesn't need any explicit OOP support -- algebraic datatypes, modules, lazy evaluation, and first-class functions give you everything you need to do the kinds of things you would use classes and inheritance for in OO languages.
To see why this is so, let's think about what OOP is. At the most basic level we have what some call object-based programming. This amounts to support for data hiding and abstract data types. Haskell's module system handles this function quite well, without any need to introduce a special concept of "class" (in the OO sense) nor private vs. public class members.
Object-oriented programming is then object-based programming plus class hierarchies and inheritance. Why are these useful?
Properly used, OOP is all about interface inheritance, not implementation inheritance. (At least in C++, implementation inheritance -- inheriting the data members and method implementations of a base class -- usually leads to bad design, and is discouraged by the experts.) (For those more familiar with Python, "duck typing" is the analog of interface inheritance for a dynamically-typed language.) Interface inheritance allows you to write procedures that operate on the base-class interface, but can be applied to objects of any type derived from the base class. Can we do this in Haskell?
Yes, we can. Let's consider the Haskell analog of an immutable C++ base class:
struct Base { virtual ~Base(); virtual int foo() const; virtual int bar(int n) const; };
class derived :: public Base { ... data members ... public: derived(T_1 arg1, ..., T_k argk); ... implementations of the virtual functions ... };
Haskell has no direct analog of object classes and virtual methods, but you can use lazy evaluation and first-class functions to achieve the same result:
data Base = Base { foo :: Int, bar :: Int -> Int }
derived :: T_1 -> ... -> T_k -> Base derived a_1 ... a_k = Base { foo = ...; bar = bar } where bar n = ...
The analog of a mutable C++ base class is a little bit more involved, but not much. Suppose that we change bar(int) to be a mutating method in the C++ Base class:
virtual int bar(int n);
The Haskell analog then changes to
data Base = Base { foo :: Int, bar :: Int -> (Int, Base) }
derived :: T_1 -> ... -> T_k -> Base derived a_1 ... a_k = Base { foo = ...; bar = bar } where bar n = (..., (derived a_1' ... a_k'))
(Here a_1', ..., a_k' are k expressions involving n and a_1, ..., a_k.)
What do the rest of you think? Is my analysis correct?
------------------------------------------------------------------------
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Sun, 2009-01-04 at 19:47 -0600, Drew Vogel wrote:
I saw a blog entry once (sorry, google can't seem to find it now) proposing the idea that OOP really is just language support for CPS (continuation-passing-style). Does anyone have a link to this?
'don't think I've heard of it, but it sounds like it should be given as much shrift as most blog articles, which is to say none at all. I can imagine something about using the duality between FP and OOP and using type negation (which is related to CPS) to witness this, but from that perspective FP is just as much "just language support for CPS," a non-sensical phrase. "Language support for continuations" does of course make sense but doesn't describe many languages, OO or otherwise.
participants (5)
-
Brandon S. Allbery KF8NH
-
Colin Paul Adams
-
Derek Elkins
-
Drew Vogel
-
Kevin Van Horn