[erlang-questions] OOP in Erlang
Thu Aug 12 04:49:56 CEST 2010
On Aug 11, 2010, at 7:44 PM, Guy Wiener wrote:
> I have to stand up for OOP at this point: OOP does not start and end with
> "encapsulating code and data". The OOP feature that has the strongest impact
> on code brevity is *inheritance*. Thus, the interest in extending modules.
I was recently reading a collection of essays by Kent Beck, one of the
big names in the Smalltalk and Patterns worlds. Oddly enough, he said
that he didn't think inheritance was all that big a deal.
Having worked on a Smalltalk system for several years, I've found that
every time a new subclass is created you have to revise up and down the
inheritance hierarchy to make sure that everything still makes sense.
The more goodies you put in the parent class, the harder it is to make
a child class that gets them all exactly right.
That is, if you *bother*. I love the Smalltalk language, but one thing
that bugs me about all the actual Smalltalk systems (other than my own,
of course) that I've tried is the high frequency of stuff in superclasses
that does NOT in fact work with subclasses. (What bugs me about _my_
Smalltalk is the effort it takes to make sure that doesn't happen.)
Here's the example that currently annoys me.
(0) Squeak 4.1 has a #closed method in Stream, that you can send to
a stream to find out whether it needs closing or not.
Some of the streams in VisualWorks 7.5 non-commercial also have this.
(1) ('abc' readStream) close; closed
returns false in Squeak. Now the way I read the ANSI Smalltalk
standard, closing a stream is supposed to release all the resources
it is holding onto. A ReadStream is holding onto the collection it
is streaming over, so closing it ought to release that.
In fact #closed is giving the right answer here, because #close does
NOT close a ReadStream. It's worse than that: when I tried to fix
this by defining #close suitably for ReadStream, a core part of the
system broke, because it relied on #close *NOT* closing certain
(2) In VisualWorks it just raises an exception because file streams do
have #closed but data structure streams don't.
(3) I thought that #closed sounded like a neat idea and decided to add
it to my system. This turned out to require the careful reinspection
of some 75 classes, and the addition of some new ones.
On the whole, I'm pleased that I'm doing this (I've done all the
input streams and am working on the output ones). Previously I
had taken far too much advantage of the fact that ANSI says sending
anything to a closed stream has "undefined" effects. Now either
something sensible will happen or you will get a defined exception.
Adding more test cases did no harm either.
The problem is that inheritance per se is easy.
Making sure that all inherited code works in all places is hard.
Conclusion: inheritance is NOT an unmixed blessing. It can, in fact,
create maintenance nightmares.
Then there's the Standard Template Library in C++.
Alexander Stepanov is on record as not having found inheritance
of much use for that. The book about programming he published
recently makes much use of "types belonging to CONCEPTS" but
none of types belonging to inheritance hierarchies.
Then of course there's CORBA, with inheritance of _interfaces_,
but no inheritance of code.
There's the interesting distinction between Haskell type classes
(which do provide 'default implementations' that you can inherit)
and the otherwise identical Clean type classes (which don't).
Oh yes, one other thing. In developing stream classes, inheritance
in Smalltalk is less help than you might expect. For example,
ReadWriteStream must provide all the methods that ReadStream and
WriteStream do, but can inherit from only one of them. And I have
a special case of that, StringReadWriteStream, which ought to
inherit from String as well, but can't. There are several other
examples of this kind. I have four copies of some methods.
Until my compiler supports Traits, I've considered using file
inclusion to handle such duplicate methods. Erlang _has_ file
inclusion, so we can use that.
In a language with hot loading, it's not completely clear what
module extension MEANS.
Suppose we have
module C extends P
Does P also get loaded, or is it just the compiler that's aware of it?
Should C change?
What if some of the functions of P have been inlined in C?
> Also, IMHO, parameterized modules are a more compact way to encapsulate code
> and data, since that you don't have to pass the data structure around as an
> explicit state (assuming, of course, that the data is immutable).
When I called myself an ML programmer, I *had* modules which took
values, types, and modules as parameters (called 'functors').
When I switched to Haskell, I found that I never missed them.
In fact with Erlang parameterised modules you DO have to pass
around the explicit state. As noted before in this mailing list,
it's just as easy, if not easier, to put functions inside a
data structure. Parameterised modules solve a problem that Erlang
did not actually have.
More information about the erlang-questions