Re: SoA Vs OO

From: Joachim Durchholz (jo_at_durchholz.org)
Date: 07/01/04


Date: Thu, 01 Jul 2004 01:41:47 +0200

I spy wrote:

> "Joachim Durchholz" <jo@durchholz.org> wrote:
>>
>> Hmm... I never saw a class library that did the things the way I
>> liked them ;-)
>
> Not a single one? What are you trying to do? :-)

Oh, simple things. Like a GUI library that sat on top of GDI and created
all things of nifty abstractions (most notably validation, a rule-based
framework for automatically resizing subcontrols when the enclosing
control changed size, a homegrown table control that could host any
other controls, and a tab order mechanism that was independent of
control creation order - none of these were mean feats *ggg*) (in
fairness, I must add that we were a team of three *ggggg*)

>> And, let me repeat: it's *not* a roll-your-own version of OO.
>> First, what's a class in an OO language is usually spread all over
>> the data structures in an FPL, in the form of single-purpose
>> functions. There is no such thing as a strictly coupled data type,
>> not in conjunction with dispatching. (FPLs have abstract data types
>> and tightly coupled, encapsulated implementations for them. It's
>> just that they don't use dynamic dispatch for it.)
>>
>> As I wrote below, it can be made into a roll-your-own version of OO
>> dispatch using records of closures. You still don't have local
>> data in these records, nor do you have inheritance, nor any of the
>> other bells and whistles (which is just as well: these records
>> simulate just dynamic dispatch, not the other features of OO, which
>> are supplied via the ADT mechanisms).
>
> Yes I got all that, but I'm still not sure exactly what your setup
> looks like.

Um... time for an example.

Say the task is to have a list of things that can display on a screen.

In the OO world, I need to have a type Displayable, with a
draw(where:Screen) method. If I need to display something that doesn't
inherit from Displayable (maybe because it's from the class library of a
third party), I have to write some wrapper code (if Displayable is a
large class with many, in this context unneeded features, what I
actually want to do is buried in tons of other code, either lots of
stubs or lots of wrapper code).

In the FPL world, I make a list of curried functions.
I wrap every object that I want to draw in a function that will draw it
on a Screen object.
So, if I have a Rectangle type that offers me
   draw_rect x,y,w,h:int; where:Screen
I say
   new_list = cons
                (draw_rect 1 15 50 60 _)
                -- invented syntax for creating a curried function
                old_list
to prepend a 50x60 rectangle at (1,15) to old_list.
If I need a circle, I do likewise.
If I need a bitmap, I do something like
                (draw_bitmap (bitmap_from_file "foo.bmp") 50 80 _)

What I get is a list of functions that will draw a given scene on the
screen. Or on something else (if the various drawing routines accept
something more general than a Screen).

For traditional thinking, this list is just a series of functions
waiting to execute.
In a functional context, this isn't less true, but the list is also just
data. It *is* the scene.

If I want to do hit-testing (i.e. associate a given point with a
geometric figure), I create a similar list. I'm free to leave out those
scene elements that are irrelevant for hit-testing (and don't have to go
back to the Displayable class and add a boolean attribute that says
whether it can be found during hit testing - besides, which elements
hit-test and which don't may vary due to circumstances, so this isn't a
really satisfactory solution anyway). I can add elements that aren't
Displayable but require hit-testing anyway (e.g. invisible areas like
"anywhere on the table line, between the controls on it").

In short, a class hierarchy forces me to make many design decisions,
quite far up in the hierarchy.
In an FPL, I can defer many of these decisions. I can defer them until I
really need to make them.

>> The advantage of not having everything in its separate type is that
>> there are less barriers for using a type. If everything is a list,
>> you can apply the standard list functions to everything, without
>> the need for type acrobatics. In an OO language, I have to lay my
>> type structure out very carefully to avoid having barriers, and
>> sometimes I'm forced to choose the lesser of to evils.
>
> And this is where we get into the maintenance issue. Making
> everything a list might do the job for now, but what if I really do
> need O(1) or O(logn/nlogn) access down the road?

That's a valid criticism. It's difficult to replace code that uses lists
with something that uses other, more advanced data structures.

On the other hand, the issue is being addressed. There are type classes
and functors (which are different ways of abstracting out commonalities
like iteration-over-a-bag and similar stuff, i.e. can be used as ADTs).

> OTOH, OO definitetly doesn't come for free. There is a cost to having
> to anticipate what might change.

Definitely :-)

>> Besides, these nitty-gritty functions will have to be implemented
>> some day. The ability to have the IDE generate the code for me just
>> defers the work, it doesn't eliminate it.
>
> We're going in circles here. If you need to implement them anyway,
> then where's the extra labour you're complaining about?

Because if I'm forced to implement them right now, at a point in the
design where I don't need them, I'll pretty sure make mistakes. I'll
want to get over with that just to get my code working, so I'll sloppily
hack something together and test it as quickly as possible (i.e. usually
not at all - I have a deadline to meet, and my superior won't understand
that I missed by a week just because I had to clean up some service
class and write code that isn't even used).
Half a year after that, this sloppy, scantily-tested code will come back
and bite me. Colleagues might rely on those nominally-working functions,
set their deadlines according to their expectations, just to find that
the code they're relying on doesn't really work.

I've been on both sides of this fence far too often to find much comfort
in the idea of "abstract everything in classes, and if a new subclass is
called for, you must *immediately* implement *everything* in it".

I could do stubs. That would have the advantage that the
untested/not-yet-done parts of the class are clearly marked.
However, this moves a large part of static checking back into the
dynamic domain - these stubs won't be detected by the compiler, they
will fail at run-time.

The functional approach is more pragmatic here. Write those functions
that you need, when you need them. Ironically, I feel FPLs are far
nearer to Extreme Programming practices in this respect than OO
languages :-)

>>> OK, I'll concede you the wilful overriding bit. That's a problem
>>> of a lack of semantic typing in most OO languages.
>>
>> Which isn't really feasible, even with Design by Contract in mind
>> (I have used it for several years, and while it's far, far better
>> than nothing, it doesn't and cannot cover all cases).
>
> Well there's always going to be exceptions in any real world problem
> (the Birds can fly/Penguins example comes to mind). You have to deal
> with them as best you can, and that might sometimes call for willful
> overriding.

Which is again a case for not doing it with type hierarchies :-)

(Oh, the contortions I had to make to accommodate such cases in an
otherwise perfectly class hierarchy... boolean members like "can_fly"
which had to be properly initialized and kept correct at all times,
assumptions that these members could never change during the lifetime of
an object that turned out to be wrong later, and - worst of all - having
to add such flags very, very high up the class hierarchy, possibly
breaking any code that introduced its own can_fly attribute, for
potentially different purposes... you're recalling some very ugly
memories in me - not that that should be considered your fault *g*.)

>>>>>>>> The advantage is: you don't have to inherit from a
>>>>>>>> common "CanvasItem" superclass that may be polluting
>>>>>>>> your namespace. [...]
>>>>>>>
>>>>>>> I am not sure what you mean by "polluting the namespace"
>>>>>>
>>>>>> If I inherit the CanvasItem class, my new subclass
>>>>>> automatically contains all the stuff that was declared in
>>>>>> the superclass, whether I want it or not.
>>>>>
>>>>> Well that goes back to the issue of whether your inheritance
>>>>> hierarchy was appropriate to start with. Again think of the
>>>>> WindowWithScrollBar class. If I add stuff to the Window base
>>>>> class, then yes I would want WindowWithScrollBar to reflect
>>>>> that too. If not, then add it privately.
>>>>
>>>> No, I didn't mean that. Say the Window class contains a
>>>> GdiHandle member. It's private since it's doing very, very
>>>> crufty internal stuff, so the subclass can't even access it.
>>>> Yet when the subclass declares a GdiHandle member, it has a
>>>> name clash.
>>>
>>> And for a very good reason! if the base class introduced
>>> GdiHandler, there's a goodly possibility that whatever code you
>>> had in your derived class may be obsoleted. So it doesn't
>>> surprise me that it breaks your derived class.
>>
>> Why on earth should the subclass care whether the superclass now
>> reimplements something that's perfectly working in the subclass?
>
> But without a semantics and theorem prover and a lot of time on your
> hands you aren't going to know whether or not it was an addition to
> or an obsoleting. If the base class was introducing a concept that
> didn't exist before and the concept had a name clash with one I
> already use, I think I'd want to at least investigate and eliminate
> the potential for confusion for future readers of my program.
> Remember also we're talking implementation inheritance here, which as
> Bertrand Meyer points out is useful - but risky.

Thanks for making my point: that subclass inheritance is unmodular :-)

>> Assuming the subclass uses the GdiHandle for something that's
>> entirely unrelated to what the superclass is doing (say, it's the
>> handle to a bitmap, while the superclass wants to refer to a cached
>> window handle) - then the class is entirely irrelevant.
>>
>> The bad thing isn't that there may not be good reasons. The bad
>> thing is that the superclass and the subclass aren't properly
>> encapsulated from each other. They cannot be if the subclass uses
>> internals of the superclass.
> If you're really bothered by it then use the public access methods

Which usually aren't available - that GdiHandle thing was supposed to be
strictly internal.
Besides, we've been talking about name clashes in the subclass. These
happen independently of whether the member is externally visible or not.

> Well if you have anything that's supposed to look like an inheritance
> hierarchy in your problem domain you've severely broken it and LSP
> doing that. So I would say "don't do that!"

Exactly.
Interface inheritance (i.e. subtyping) can be a useful tool to
streamline interfaces.
Implementation inheritance is - according to my experience - not worth
the risks. It should be replaced by good abstraction and reuse
facilities that use other means (e.g. modules that can be parameterized
with types and values, where the values might be functions or closures).

Of course, that's just my personal view :-)

> I am not denying that the orthogonal decomposition you have outlined
> isn't useful. But which way I break it up should be a function of
> what my expectations are for the software I am designing. I don't
> like having it forced on me by language shortcomings

If implementation inheritance is the only or the main way of reusing
code, than *that* is a language shortcoming.
The alternatives that I named in the above paragraph are just as
convenient (if done properly, syntactically and semantically), but far
more safe.

IMNSHO ;-)

Regards,
Jo



Relevant Pages

  • Re: SoA Vs OO
    ... Say the task is to have a list of things that can display on a screen. ... It's difficult to replace code that uses lists ... in the idea of "abstract everything in classes, and if a new subclass is ... > Remember also we're talking implementation inheritance here, ...
    (comp.object)
  • Re: SoA Vs OO
    ... > Say the task is to have a list of things that can display on a screen. ... > quite far up in the hierarchy. ... It's difficult to replace code that uses lists ... >> Remember also we're talking implementation inheritance here, ...
    (comp.object)
  • Re: SoA Vs OO
    ... > Say the task is to have a list of things that can display on a screen. ... > quite far up in the hierarchy. ... It's difficult to replace code that uses lists ... >> Remember also we're talking implementation inheritance here, ...
    (comp.programming)
  • Re: SoA Vs OO
    ... data type, they are just functions that operate on a data type, and if ... When doing functional design, you start with what you want done. ... mostly it's lists and trees (there isn't much need felt ... so the subclass can't even access it. ...
    (comp.object)
  • Re: SoA Vs OO
    ... data type, they are just functions that operate on a data type, and if ... When doing functional design, you start with what you want done. ... mostly it's lists and trees (there isn't much need felt ... so the subclass can't even access it. ...
    (comp.programming)