Thursday, December 27, 2012

Ask not what an object is, but...


I can barely remember the days when objects were seen like a new, shiny, promising technology. Today, objects are often positioned between mainstream and retro, while the functional paradigm is enjoying an interesting renaissance. Still, in the last few months I stumbled on a couple of blog posts asking the quintessential question, reminiscent of those dark old days: “what is an object?”

The most recent (September 2012) is mostly a pointer to a stripped-down definition provided by Brian Marick: “It’s a clump of name->value mappings, some functions that take such clumps as their first arguments, and a dispatch function that decides which function the programmer meant to call”. Well, honestly, this is more about a specific implementation of objects, with a rather poor fit, for instance, with the C++ implementation. It makes sense when you’re describing a way to implement objects (which is what Marick did) but it’s not a particularly far-reaching definition.

The slightly older one (July 2012) is much more ambitious and comprehensive. Cook aims to provide a “modern” definition of objects, unrestricted by specific languages and implementations. It’s an interesting post indeed, and I suggest that you take some time reading it, but in the end, it’s still very much about the mechanics of objects ("An object is a first-class, dynamically dispatched behavior").

Although it may seem ok from a language design perspective, defining objects through their mechanics leaves a vacuum in our collective knowledge: how do we design a proper object-oriented system?


On one end of the spectrum, we find people quibbling about increasingly irrelevant details about what an object is or is not (just see some of the comments on Cook's post :-), while on the other end, we find programmers abusing objects all the time, building “object oriented software” based on manager classes and get/set methods, with just a few principles as their beacon toward the promised land.

Still, it's not my intention to question those definitions of objects. In a more radical way, I say we should redefine the question, not the answer.


[Optional] A little history…
(Feel free to skip this and jump to the next paragraph, if you don’t care much for the past. However, Alan Kay will promptly tell you that without history, it's just pop culture.)

Do we actually need to dig deeper into the definition of object orientation? Isn’t object oriented mainstream now? Isn't it true, as Cook says, that “The fundamental nature of objects is well-known and understood by the OO community”?
I tend to disagree. Bad object orientation is mainstream, but good object orientation is rare. Open up the Android source code (just to mention a relatively recent large-scale development) and you’ll see that 5000 LOC classes are the norm, more than the exception.

So, maybe the question is not really the best one. Maybe we shouldn’t ask what objects are, but what object orientation is about. Surprisingly, or maybe not, most literature simply equates object orientation with the adoption of an object oriented language, adding very little to the conversation. There also a few paper defining OO as "modeling the real world", which leaves a lot to be desired, but overall, the literature is mostly focusing on mechanisms (inheritance, polymorphism, classes). Sure, we have the usual blurb on principles, but that's more of a band-aid and can only help so much.

Facing dissatisfaction with contemporary literature, sometimes I turn to history for inspiration. For instance, we may just look up Alan Kay for the ultimate definition of what object orientation is about, right?

Wrong :-). Alan has always been elusive about defining OO. Feel free to peruse this comprehensive wiki page, but don't feel ashamed if you leave with a sense of inconclusiveness. We could also look up an interesting email exchange between Stefan Ram and Alan, ending with "OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things", which is better than nothing, but yeah, not really a great guidance when you're designing a system.

Well, maybe it's just old stuff. However, even recently, answering a (ridicolous) critique of OOP incredibly featured on Communications of ACM, Alan played the "misunderstood" card and said “the remedy is to consign the current wide-spread meanings of “object-oriented” to the rubbish heap of still taught bad ideas, and to make up a new term for what I and my colleagues did”. Ouch. 

Admittedly, he gets around some concepts like messaging again, but honestly, we can't blame people for going with mechanistic definitions if we cannot provide a good alternative.

Well, if Alan won’t cut it, perhaps his nemesis (Bjarne Stroustrup) will. After all, Bjarne wrote a promising What is ‘‘Object-OrientedProgramming?’’ paper, revised in 1991. Still, the paper is largely language-centric, and the general idea is to equate ‘‘support for data abstraction’’ with the ability to define and use new types and equate ‘‘support for object-oriented programming’’ with the ability to express type hierarchies. Not what I was looking for.

Well, maybe even “what is object orientation” is simply not the best question.

What if…
I guess you’re all familiar with this kind of structure:


It’s that saddle-shaped thing, made of cables and membranes, often used to cover stadiums and other large areas. The technical term is tensile structure.

Now, if you’re in computing, you can just call that a new construction paradigm, show a few examples of saddle-shaped things, then move to the much more interesting field of cables and membranes, and spend all your time debating about PTFE-coated fiberglass vs. PVC-coated polyester and how only a complete moron would ever consider creating a tensile structure in anything else than an ETFE film.

But this is not computing, so we can find a much better definition of tensile structure. You just have to fire up Wikipedia and step into an entire new world. The page begins with a precise definition of tensile structure, based on forces and reactions to forces: 

A tensile structure is a construction of elements carrying only tension and no compression or bending”. 

There is an immediate distinction with tensegrity, which carries compression as well. Only after a certain while, you'll find a discussion of materials, like membranes and cables, and what is more appropriate when you want to build a tensile structure.

A precise definition like that removes any possible source of confusion. Being tensile is not about the saddle shape: you can't just mimic the shape of a tensile structure and claim that it is indeed a tensile structure. Besides, tensile structures don't have to be saddle-shaped.
It's also not about the materials. It’s not strictly necessary to use membranes and cables; it's just that, given our current understanding of materials, those seem like a very good fit.

Interestingly, the definition is short yet powerful. It provides guidance when you design structures and guidance when you choose (or even better, design) materials.

The real question
So maybe we should not ask "what is an object" and then define object orientation as "the dynamic creation and use of objects" (the quote is a bit out of context here – Cook was talking about languages – but you get the idea). Neither we should ask ourselves “what is object oriented programming” and define it in terms of language features. That would be very much like talking about cables and membranes and then say that if you build it with cables and membranes then it's a tensile structure. It appeals to people who want to reason at the material level (language and constructs), but it's neither precise nor really useful for the software architect.

So the real question should be: what is an object oriented structure? Once we know the answer, we can freely discuss how different materials (languages, constructs, etc.) can help us building an OOS, and how they help, and how they don't.
Ideally, the definition of OOS would also bring some clarity on what we can expect out of an OOS and what is not to be expected. For instance, we do not expect tensile structures to sustain heavy snow load.
We could also try to define the nature of a functional structure, and compare the two without (too much) advocacy. Same thing for the logic paradigm, still waiting for its own little renaissance. Or for Aspect-Oriented structures.

The problem, of course, is that we don’t know how to do that. We don’t have a theory of forces. We don’t have clear notions about how software materials react to forces. So we end up building an entire industry based on advocacy, more than on science. It doesn’t have to be like that, of course. Seneca used to say “It is not because things are difficult that we do not dare, but because we do not dare, things are difficult”. So let’s dare :-)

Interlude – the Three Spaces
I’ll assume that most of you guys are not familiar with my work on the Physics of Software, so I’ll recap a simple notion here, that will be useful in understanding what follows.

Software is shaped by decisions. Decisions about the function (what the software will do) and about the form (how we are going to do that). Some decisions are taken after careful consideration, some by routine, and others by feedback (from the users, or from the material). Decisions have this nasty habit of being unstable. Features will be added, removed, updated. We’ll change the form by refactoring. Etc. At any given point in time, our software is just the embodiment of a set of decisions. I consider that as a single point into a multi-dimensional decision space. When decisions change, we move to another point into the decision space.

Software is encoded in artifacts. It doesn’t really matter if you write procedural, functional, or OO code. It doesn’t really matter if you use models or code. We cannot manipulate information; we can only manipulate a representation of information (artifact). Languages and paradigms define the structure of their artifacts, and provide modular units. A function is a modular unit in the functional paradigm. A class is a unit in the OO paradigm. For various reasons, I tend to call those units “centers”, which is a more encompassing concept in the physics of software.
In the end, a point into the decision space is encoded into a set of artifacts in the artifact space. Those artifacts define a set of centers.

As we know, the same run-time behavior can be obtained by widely different organizations of artifacts and centers. In the end, however, the knowledge encoded in those artifacts will be executed. Things will happen, on real hardware, not in some theoretical semantic space. Things like cache lines and multicore CPU etc. will come to life and play their role. I call this the run-time space.

Now, to cut it short, a change in the decision space will always be about the run-time or the artifact space, but it will always be carried out through a change in the artifact space. I want that new feature: it’s about the run-time space, but I need to add code in the artifact space. I want to refactor that switch-case into a polymorphic class hierarchy. It’s about the artifact space – the run-time, visible behavior won’t change a bit – and it’s also carried out in the artifact space.

In the physics of software, we acknowledge that materials (the artifacts) are shaped by decisions. Therefore, those decisions are the forces acting upon the materials (following D’Arcy – it’s a long story :-). Those forces will materialize as changes

So, what is an Object Oriented Structure?
Ok, this is my best shot as of December, 2012. Maybe I’ll come up with a better definition at some point. Or maybe you will. I'm totally interested in improving it, but not really interested in a fight over it :-), so feel free to chime in, but please be kind. Note that I'm not trying to say that OO is "good" or anything like that. I'm just trying to define what an OOS is, from the perspective of forces and reactions to forces.

Given a Change H (in the decision space), H will occur with some probability P(H). Generally speaking, P(H) is unknown, but in many cases we can reasonably classify P(H) in discrete sets, e.g. “unlikely”, “possible”, “very likely”.
H will be about the Run/Time space or about the Artifact space, but will always entail some work (a change again) in the Artifact space. Let’s say that H will require a change (creation, deletion, or update) in artifacts A1...An.

Now, given a set of (meaningful) changes H1…Hk, occurring over time, an OOS is one that minimizes the total size of the artifacts you have to update, encouraging the creation or deletion of entire artifacts instead. Slightly more formally, within the limits of HTML text, an OOS will try to minimize:

Sum( j = 1…k) { P(Hj) * SUM( i = 1…n ) { Size(Ai) | Ai is updated in Hj } }

A couple of notes:

  • Cheating is not allowed. If you delete a module and add it back with some changes, it counts as an update, not as 1 delete + 1 create.
  • Note the P(Hj) factor. It accounts for the fact that it’s not always possible to balance all forces, and that a good OOS accounts for the most likely changes.

Does it work?
I understand that this definition may not look like OO at all. Where is polymorphism? Where is encapsulation? Where is inheritance? Well, that's the material we use – the cables and membranes. A good definition of OOS must be beyond that.

Let’s use, once more, the familiar Shape example to explain why the definition above is aligned with Object Oriented Structures, while other structures tend not to comply with it. Say that we have a set of shapes: Circle, Polygon, perhaps some specialized polygons like Square and Triangle, etc. We want the ability to create one of those shapes, with a given position and size. We also want the ability to calculate the bounding box of that shape. Given that, we also want to get a bunch of those shapes, and distribute them evenly in space, like many programs do. Of course, that's just the beginning, we know that more features (and more shapes) are coming; we just don't know which features and which shapes.

What are our basic needs? Well, any shape is defined by some data, so we need to put those data somewhere. We also need to implement the bounding box logic. We also need to implement the "distribute evenly" logic. That requires the ability to move shapes around, of course. We can shuffle around that logic in different forms, of course. That's a design decision.

The decision space is usually very large, even for a small program like this. However, a few macro-decisions can be easily identified:

    1. We may choose a uniform representation for all the shapes, or let every shape have its own representation (if needed).
    1. We may have a separate implementation of the bounding box logic for each shape, or just a single center / artifact. That single center / artifact may or may not have to know about every shape type.
    1. We may have to know about every shape type inside the "distribute evenly" logic. Or not.

Let's go through this list with a little more attention to details.

1) One might trivially say that every shape is just a list of points. Oh, the magic of lists. Except that, well, we don't want to turn the circle into a list of points, and maybe not even the regular polygons. Of course, we can still shoehorn a circle in a list of points. Put the center as the first point and the radius as the second point; or maybe any given point on the circumference will do. Or use 3 points to define the circle. But other things are easier when you store the center and radius (think area).
That's sort of misleading though. It's uniform representation with non-uniform behavior. For instance, when you want to move a circle you only move the center. An irregular polygon requires that you shift all the points. Also, what if we want to add a shape that is not easily defined by a list of points, like a fractal curve? You can choose a uniform representation if you want, but then you also need to store the shape type somewhere, and have some logic dealing with specific shape types, usually by switching on the shape type.

2) Calculating the bounding box for a circle and for an irregular polygon requires a different logic. We can have one big function switching on types and then using the uniform representation to carry out non-uniform behavior, or we can have a separate logic for each shape type. Then it all depends on how we want to invoke that logic: by switching (again) on shape type, or in some other way.

3) What is a reasonable implementation for the "distribute evenly" thing? Well, to be precise, it's usually a "distribute horizontally" or "distribute vertically" thing. Say that it's horizontally. A reasonable abstract algorithm is:
- take the bounding box of every shape involved
- add the width of each box together so you know the required space
- take the bounding box of the bounding boxes; take its width; that's the available space
- calculate free space as: available - required
- divide by number of shapes - 1; that's the spacing.
- start with the leftmost and space shapes accordingly, by moving their left side

Most of the logic is in terms of bounding boxes; basically, the only things dependent on shape type are the calculation of the bounding boxes themselves, and moving shapes by the left side (different for circle and irregular polygon, again). Once more, we have choices: add a switch inside the distribute function (ugly), call a move function that is switching inside, or do it some other way that does not require a switch.

Ok, we're set. Let's see if the definition of OOS above will tend to favor an OO implementation over the alternatives.

Create, don't Update
A very reasonable, likely Change is that we want to add a new shape type. We may not know the probability P(H), but we can say it's likely. If we want to do that by adding a new artifact, without changing existing ones, any kind of switching is forbidden. That includes the common cases of pattern matching, including case classes and the like, as they normally require updating the list of matching (it doesn't have to be so – more about this later).
So, the definition above basically forbids the switch/based (or match/based) implementation. There is relatively little choice: you need some form of late binding. You want to loop over shapes and ask for a bounding box. You can't look inside because that requires a switching over shape type, to make sense of the pseudo-uniform representation. You want to ask for the bounding box (a service, not data, guess what :-), but you don't (can't) know the function that will be called. Of course, the obvious OO way to do that (a Shape interface, a Circle class, a RegularPoligon class, etc) is a very reasonable way to comply with this.

Localized change
The prophets of public data and uniform representation won't tell you, but as you add new features, you may want to improve and adapt the internal representation. For instance, calculating the bounding box for a circle is simple math and you may want to do that on the fly; for an irregular polygon, you have to go through all the points, and you may want to cache that thing. It's a Change you may want to take at some point. You can't do that by merely adding a new artifact: you have to go in and change an existing one. Or many.
How do you estimate the impact? A proper OOS requires that this change will impact as little code as possible (see the artifact size in the formula). Well, if you:
  • expose a BoundingBox service, not your internal data
  • actually hide your data with some modular protection notion
you're pretty sure that there will be only one thing to change: the IrregularPolygon abstraction, and inside that, the BoundingBox function only. So you need some form of encapsulation, the modular hiding of data behind stable services. Once again, the obvious class-based design is a good way to comply.
As you know, most OO languages have a notion of public and private, and sometimes protected. You can easily see those notions as ways to limit the potential impact of a change. Theoretically, if a Change requires an Update of a Private part, that change will be local, which is ok with the idea that an OOS tend to minimize the Update, not to eliminate the Update. Of course, for that to work, you really need to create an OOS. If you expose the private part through a Get/Set, the Update won’t be local anymore, reflecting the fact that the structure is not really OO.

Occam's Razor
Do we need an abstraction for the pentagon, one for hexagon, one for every regular polygon? Or is it better to have only one? Once again, look at the reality of development, not at some philosophical idea. The reality of development is that you may not have got the best Shape interface from the very beginning, and that you'll change that. Maybe you didn't think of a "move left side" service in the beginning. Now you have to go in and add it to your shapes. That's a bad change from an OOS perspective: you change many artifacts (more on this later). Still, the definition is telling you to avoid unnecessary classification for the sake of it. It's asking you to keep the number of artifacts (the cumulative size, actually) to a bare minimum. It's actually suggesting that in most cases, reusing a common implementation is good, because then you have only one place to change. Some kind of implementation inheritance will help balance this need with the need to specialize behavior (in some cases).

You don't need to "encapsulate" the Bounding Box (too much)
A large part of the "distribute evenly" logic is based on bounding boxes. A bounding box is an object and yeah, sure, it's nice to expose a few methods like height() and width() but, on the other hand, it's no big deal if you expose (perhaps read-only) the coordinates of top / left / right / bottom corners. Sure, strictly speaking, that would be a "violation of encapsulation", which is the kind of primitive and limited reasoning that principles tend to inspire. Once you look at the definition above, you'll quickly understand that the probability of change inside the bounding box, and especially the probability of that change having an impact on other artifacts, is pretty much zero. It's a stable abstraction, whereas Shape is an unstable notion (for a few hints on instability types, look up my previous post "Don't do it"). So, let's be honest: a few methods in BoundingBox won't hurt, but "exposing data" won't hurt much either.

You need to align the OOS with the force field
A very reasonable request would be to have a vertical version of the "distribute evenly" feature. The algorithm is basically the same, except now we have to move shapes by the top, not by the left side. That's a breaking change, and it's not localized. It's an high-impact change, because the obvious OOS (Shape interface + concrete classes) is well-aligned with the need to extend shape types, but not well aligned with the need to extend shape methods.

We can look at this under different perspectives:

  1. We can’t protect ourselves from every possible orthogonal change in the multi-dimensional decision space. Not with “regular” OO anyway (and no, not even with regular FP). So we drop in the probability, and create a structure which is well-aligned with the most likely changes.
  1. We need better materials. Open classes, selective protection, etc. Understanding forces will guide us better. For instance, Scala-like case classes are a bad idea if you’re aiming for an OOS (because of the switch/case, of course, not because of the pattern matching itself). Find the real good idea (it's rather simple).
  1. OOP is a scam. It doesn’t work. Let’s move en masse to FP. Wait, what is a Functional Structure again?

Note that it would be wrong to say that OOP can’t sustain instability on the functional side. The well-known Command pattern, for instance, is an OOS dealing specifically with a set of functions that is known to be unstable (high probability of accretive change). OOP can’t deal well with orthogonal changes, unless we improve the available materials. AOP, for instance, was a definite step forward on that area.

It is worth considering that, in many cases, instability in some areas is temporary. Very much like atoms in an annealing process, our design is often unstable in the beginning, as we try to understand the "right" partitioning and the "right" responsibilities. In many cases, instability on methods does not go on forever, as we can find a kernel of stable methods (I often say that the kernel tend to "saturate"). After that, we can build algorithms outside the kernel, like the "distribute evenly" thing. By the way: where do we put those algorithm in a proper OOS?

Mainstream OOP is a good fit, but not the only possible one
Whereas the discussion on "what is an object" tends to put strong boundaries on our materials, focusing on forces and reactions to forces is really liberating. The following mechanisms tend to be useful to build an OOS:
  • selective protection; not necessarily the "closed" kind provided by classes
  • late binding; but genericity is useful as well, in some circumstances
  • code reuse mechanisms, like inheritance, prototypes, mix-ins, delegation (ideally, the kind of automatic delegation that does not require manual construction of proxies), and higher-order functions.
  • generally speaking, indirection mechanisms (I'll get back to this in a future post), including, of course, closures and lambdas.
  • add your stuff here : )

What hurts: whatever makes you build an hourglass shape. Switch/case, the wrong approach to pattern matching (see above), etc. Whatever leads to duplication and non-modular decisions. Early-binding of things (for instance, having to specify a class name in code to get an instance). Etc.

It also goes without saying that the adoption of a mainstream OO language gives no guarantee that we're going to build an OOS. The state of practice largely proves that.

But… why should we do this?
Sorry, this is already a long post. Understanding why an OOS as defined above is, let's say… interesting, is left as the dreaded exercise for the reader.

Beyond principles
You can easily see how many principles follow directly from this definition:
  • Demeter's law (trying to minimize the impact of some updates)
  • Information Hiding (same as above)
  • Don't Repeat Yourself (for unstable code, I should say)
  • Etc etc.
As I said many times now, I think it's time to move beyond principle and toward a better understanding of forces and properties in software materials.

Is this really the Smalltalk way?
No, absolutely. Smalltalk aimed at something bigger than this. In a sense, Smalltalk tried to apply the same rule to the language itself, in a sort of reflective way. To really understand what Alan meant by "extreme late-binding of all things", you need to realize that in Smalltalk, "if" (ifTrue actually) is a method, or a message in Smalltalk speaking.

The message is sent to a Boolean object, and a code block is passed as a parameter. Yeap. A code block. Talk about extreme late binding of things. The meaning of that code block is up to the ifTrue method. It's not a compile-time decision. This is way beyond what we usually do with most mainstream OO languages, although there has been a recent (late) move toward similar concepts.
Building a language this way means we can change the language itself by adding things, instead of changing a compiler (or interpreter). Paradoxically, languages that proclaimed to be "pure OO" like Java were actually a step backward from this, even when compared to C++. Funny, in its own way.

A definition based on entanglement
For those of you more familiar with my work on the Physics of Software, here is a bonus :-) definition based on the notion of entanglement:

An Object Oriented Structure is a construction of centers carrying mostly */D and */C entanglement between the decision space and the artifact space. When */U entanglement is carried, the size of the involved artifacts is probabilistically minimized.

There are also a few corollaries that follow directly from the definition of OOS, but that might be worth stating:

An OOS reacts to growth in the problem space with Creation in the artifact space, not with Update in the artifact space.

An OOS reacts to enumerative variation in the problem space by creating an isomorphic set of centers (often, classes) in the artifact space. This is strongly related to the enumeration law.

An OOS does not force a common structure when common behavior suffices. Optimal representation can be pursued with local changes within each center. An OOS hides the chosen representation inside the centers whenever the representation is potentially unstable.

An OOS allows the targets to make sense of the request, freeing the source from knowledge about the target types. Hence, an OOS hides the nature of the center outside the center itself (it's a Shape, not a Polygon).

An OOS will react to compositional behavior by forming compositional structures, not by creating longer functions (see some patterns: composite, chain of responsibility, decorator, etc).

There would be more to say here, including some ideas on an economic theory of software design, but this is a blog post, not a book :-).

Surprise!
This post is actually “Notes on Software Design, chapter 17”. Ok, you don’t have to go back and read the other 17 chapters (there is a chapter 0 as well). But you may want to help the world by sharing this post :-)

If you liked this post, you should follow me on twitter!


Acknowledgment
The tensile structure picture is provided courtesy of Denver International Airport.

30 comments:

Anonymous said...

Carlo,

your post is great, as always.
Your approach to the question is very constructive for the designer.

Over the years, you taught me that proper OO systems have many properties: extensibility, reusability, testability, comprehensibility and so on. But it seems to me that your definition focuses only on one of them (i.e. extensibility). Do you think that's the main characteristic of such systems?
On the other hand, are you sure that no other paradigm satisfy your definition? Maybe AOP does. Maybe an event oriented system does (I'm just guessing, because I don't know them deeply).

Thanks,
Daniele

Anonymous said...

Hi Carlo

"but this is a blog post, not a book"

Do you plan a book to present your theory? Although I know it's hard and time consuming to write, edit and than publish a book. Now I have no ather alternative :), than collecting the chapters of software design published on the blog for further reading, but it's very hard because I'm constantly distracted by the other valuable posts.

I found your writings in a comment of Rich Hickey's presentation on InfoQ: Simple Made Easy, and I have to say that I found a goldmine :).

Happy New Year to everyone.

thanks,
Csaba

Carlo Pescio said...

[part 1]
Daniele: excellent questions; you're giving me a chance to clarify a few things better.

Good software has many interesting properties, including those you mentioned. However, my goal was not to define good software, or good object oriented software, or to somehow make the equation good = OO. It was to define the core notion of an object oriented structure.
The same applies to constructions. A good construction has many interesting properties; for instance, there has to be some balance in the way the global volume is being split in sub-volumes. That is not part of the definition of a tensile structure. Of course, one can build a crappy tensile structure, for instance by splitting the global volume in tiny sub-volumes. It's still a tensile structure.

That said, I understand that my example is skewed toward extensibility, and that may be misleading. But the definition is as much about extensibility (C/D) as it is about localized changes (the probabilistic minimization of U). Kevin Rutherford asked me over twitter if this definition isn't just a restatement of the Open/Closed principle. I don't think it is, but the usual problem with principles is that you can interpret them in subjectively different ways. I see this definition covering many other principles, including Demeter's Law, in ways that have nothing to do with extensibility.

Here is an example, taken from my post on the Sump Pump problem ("life without a controller, episode 1"). This material was actually part of my first version of the post, then I cut it out because people keep complaining about the length of my writings :-).

Carlo Pescio said...

[part 2]
Early on in that post, I introduced a SumpProbe abstraction. That abstraction was meant to hide the way we measure the water level (a decision). Do we use two digital sensors, or one analog sensor? That’s hidden inside the SumpProbe. So we can now appreciate a more continuous scale of object orientation:

No OOS: we just expose the two digital sensors from the very beginning, as this is how the problem is specified. We let other portions of the software know about this decision. When we change our mind (a decision) and use an analog sensor instead, we’ll have to Update a number of other artifacts.

OOS, step 1: we introduce a SumpProbe abstraction, say a concrete class. The decision is now hidden inside that class. When we change our mind, we still have to Update one small abstraction, but overall the Update is largely minimized. It's not about extensibility. It's about localized change.

OOS, step 2: I only mentioned this in that post, but of course we can have a SumpProbe interface and different concrete classes, e.g. one based on analog and one on digital sensors. In that case, a change in the decision space is mapped to a Creation / Deletion of a class. That’s a better OOS. You just have to weigh the additional cost of doing so (compared to Step 1) with the benefits of doing so, considering the probability of change as well. Oh, look, it’s the seed of an economic theory for software :-).

Carlo Pescio said...

[part 3]
Note, however, that there is also a tension between OO and some of the properties you mentioned. An OOS, to satisfy my formula, needs to split executable knowledge in small parts, so that you localize change, or can swap in new concepts. To make a long story short, that quickly leads to delocalized plans, making reading more difficult (that is, to R/R entanglement in the artifact space). Conversely, I believe that one of the defining properties of a procedural structure is the minimization of R/R entanglement in the artifact space (and you can see that in the usual rant of people who want to "see all the logic in one place").

About other "paradigms" being characterized by the same properties. I know it's hard to let go of such things as principles and paradigms, and that one is always tempted to structure new ideas from the mindset of previous knowledge. However, to truly understand this stuff, you somehow have to let go of those notions, because they're too primitive and muddy. If I use a sheet of metal instead of a membrane to build a tensile structure, it's still a tensile structure. It's not a "metal paradigm" because now I'm using metal for both cables and membranes.

Carlo Pescio said...

[part 4]
Case in point: you can use Aspect-Oriented materials (aspects, pointcuts, etc) to build a system where when you change a decision, you have to change a very large Aspect, cutting/joining on a lot of other things. That would not comply with the definition of OOS, because the change is localized to one aspect, but the aspect is big. You can also use the same materials to build a system that complies with my definition of OOS. In that case, you're using aspects to build an OOS (that's why I've often said that having both OOP and AOP allows us to build better OOS).

The software space is intrinsically multi-dimensional. These is true also in construction, to a certain degree. You can build a "green building" or not. That's a dimension. Being tensile or not is another dimension. Etc. Same thing in software. You can build an event-based system which is also an OOS, or not. Defining the nature of an EBS (assuming there is such a thing) would be a very interesting intellectual exercise. In my current understanding of things, some structures needs to be defined with reference to the run/time space as well. For instance, one of the tenets of functional programming (immutability) is that there is no */U entanglement in the run/time space.

Carlo Pescio said...

Csaba: a plan is probably an overstatement :-), let's say that I have a desire to. I understand that blog posts are not the perfect vehicle / format for this stuff. On the other hand, quite a few portions of this theory are still underdeveloped, and some old portions are now obsolete. I may end up publishing some sort of "dynamic book", more like a dump of my notes on this subject that will eventually evolve into something more stable and organized. On the positive side, it would be a free book :-).

Unknown said...

Carlo,
the great thing about your definition about OOS is that it doesn't mention objects at all.
In a certain way, this is suggesting me that you should elide OO and find a different name.
Will MOS (Matter Oriented Structure) do?

Unknown said...

Brilliant! :D
I can easily see how this definition fit well with the mantra about the right tool in the right place at the right time (use OO mechanics when it's ok to do so, or FP when it's usefule, etc). But have you ever wonder if there's some other mechanism that maybe alone may support better the concept of OOS?

Carlo Pescio said...

Claudio: I guess it's better to stay with "OO" for the time being, but trying to find a different name in light of the above is indeed interesting :-)

Trying to mirror the idea behind the naming of a tensile structure (name the structure after the forces it tend to carry) I would probably call this stuff an Artifact-Creational Structure.

Carlo Pescio said...

Fulvio: one ring to rule them all? : ))

Honestly, it's a really hard question that can only be answered within some outer reference system of values and beliefs. For instance, if you value type safety, then it's hard to avoid something like generics (you know the problem with polymorphic collections). But generics alone won't give you late binding. Etc.

Of course, things get simpler if you change your reference system. It's not by chance that languages which attempted that before (smalltalk, lisp, etc) were basically all dynamic.

Once you beef up your wish list enough, the answer is probably "no". On the other hand, a strong orthogonality of concepts + a clean mechanism for meta-extensibility might by more interesting in the real world :-)

Anonymous said...

"values and beliefs"? Well, honestly, I'd prefer to think about type safety not as a "value" but as a *choice* with its consequences. Am I too rational? :-)

Daniele

Anonymous said...

Carlo,

I get your point.

I can see your definition is about extensibility and localized changes. I agree the comprehensibility is reduced by the splitting of information in centers (but with comprehensibility I was referring at the similarity between domain and software classes). Finally I think that testability derives from the extensibility property.
Still, I can't see the reuse. I *know* that an OOS has reusable centers. But I can't derive it from you definition.
Sorry, but for me it's important to find in your definition the properties I expect from a OOS. I guess that's the problem in defining a term used (abused?) for decades.
Unfortunately, a definition of a term that lacks a definition is right by definition :-) Are you sure you don't prefer to follow the advice of good ol' Kay and define a brand new term? Maybe something like "incremental system" or "continuous system" (in the mathematical term of continuity)...

About EBS, I think that an event based system is included by your definition, because events are a form of late binding like the polymorphism. Obviously, it depends on *how*you use events. But this applies to objects too.

Thanks a lot
Daniele

Carlo Pescio said...

Daniele: if you try to write down those consequences and their ramification, you'll quickly see that your "choice" becomes rapidly intractable by your bound rationality : ). That's why people are still debating the issue after decades of experiences with both. Real experimental evidence, as usual in software, is nearly absent.

There was a point/counterpoint in IEEE Software back in 2007, with Ungar defending dynamic languages. If you can get a copy, you'll see how quickly values and beliefs come into play : )

Carlo Pescio said...

Daniele: interesting question. I would say that reusability is an emerging property of an OOS according to the definition I gave.

Say that you want a new feature F. That's a Creational change in the decision space. Say that, according to the definition of OOS, you can carry that out with a Creation in the artifact space (say, a new artifact A).
Say that over time, other changes in the decision space do not impact A, again according to the definition above. Isn't that a good indication that A is reusable whenever you need F? (black-box reuse).
Say that over time, you need variations of F, so you refactor A into A', in such a way that F1..Fn can be carried out by Creating A1..An instead of tweaking A over and over. Isn't that a good indication that A' has become a unit of white-box reusability?

You're right when you say that I'm trying to "formally" define things that lack a commonly agreed upon definition. Even "reusability" lacks a formal definition :-). I still believe is doable, without a global reset of the discipline (which would make things simpler, but alienate even more people :-)

@ramtop said...

Very interesting read and definition.

One aspect which is very important for me is the debug/refactoring phase of our work.
For me the code is good (OOP or FP doesn't matter) if it makes easy to spot bugs (aka sideeffects) and hard to create new ones.

In this perspective a good OO design is one which not only minimize the size of a change for a new feature or to fix a bug but also help you in localize where to change quickly.

Carlo Pescio said...

Uberto: as you said, here we're moving outside a definition of OO and toward one of the many properties of "good design". The property you mention is not inherent in OO and is therefore not part of my definition.

Actually, as I said to Daniele above, many ingredients in OO (late binding, emerging behavior vs managed behavior, generally speaking the entire issue of delocalized plans as they're known in software comprehension literature) are working against your goal of pinpointing the exact reason for a bug, quickly. FP largely suffers from the same issue, through higher order functions, closures, continuations, etc.

It's hard to optimize for all properties. In practice, you always have to choose. Of course, practices like unit tests help in your case, but that is an extra-linguistic mechanism...

Anonymous said...

Hi :-) I've not read everything yet, but last year at school I studied Aristotle's metaphysics and I found a remarkable similarity between OO and the way he sees the world.
In Aristotle every entity has two properties that are form and matter(and well, l'"essenza" :D).
The form can be seen as the public interface of a class(i.e. the public methods and properties) while the matter can be seen as what composes the entity, i.e. private data of the class.
Moreover, every concrete object(that has existence) is what Aristotle calls a synolon between form and matter: in OOP, this could be the istance of a class, or what an object is. :D

Carlo Pescio said...

Lumo: yeap, there is a long trail of papers talking about OOP from the philosophical perspective of classification, usually beginning with Plato and Eidos = classes. See this paper from Antero Taivalsaari for an interesting overview, concerned with prototypes as well (as opposed to classes)

http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.56.4713&rep=rep1&type=pdf

More recently, it has been suggested that Aristotle would have found Functional Programming better :-). See video here:

http://www.infoq.com/presentations/Philosophy-Programming

The discussion is very introductory and lightweight for those with a background in philosophy, but the presentation style is funny and gets some concepts across really well :-)

Anonymous said...

Thank you, Carlo: your explanation is clear and convincing. I will adopt your definition :-)

Daniele

@ramtop said...

Thanks for the answer Carlo. I think I understood better now what you meant.

Still one thing bothers me:
"OOS is one that minimizes the total size of the artifacts you have to update, encouraging the creation or deletion of entire artifacts instead."

This is also true for (good) Functional programming, where the artifacts are functions/monads rather than objects.
Is there something in the concept of Object which make it special?

Carlo Pescio said...

Uberto:

- try to implement the shape thing in a functional language without OO-like features, like original LISP. Then go and add a new shape type. See if you can keep your artifacts unchanged.

- Do the same in a functional language heavily based on pattern matching. See if you can keep artifact unchanged. Etc.

- Implement the sump pump in a language without encapsulation, where exposing data is actually encouraged. See what happens when you change from two digital sensors to an analog one.

Note: this is not to criticize FP. FP was simply born out of different criteria. Try to get something that reacts to change as the OO version does, and you'll probably find yourself implementing objects in your FP language...

@ramtop said...

Carlo, to be honest I think I can do it in Clojure (without using any OO feature), but I'm not sure so I'll try and I'll be back to you. I'm still a novice in Haskell but I'd like to try it too.

I see the main difference between OOP and FP in the state management, inside the objects themselves in the first case and as data passed around in the second.
But a good functional program doesn't need to change code for adding a new functionality, just add a new function.

Carlo Pescio said...

Carlo, to be honest I think I can do it in Clojure (without using any OO feature)
--
... and without building an OO-like structure using closures, etc. Give it a try :-)

Note that we're actually saying different things. You say "a good functional program doesn't need to change code for adding a new functionality". I say "when you change a decision". And I don't speak about "good" at all, but that's too long a story for a comment.

That said, I'm totally interested in how your FP program will handle the addition of a new shape type, within the [realistic] constraints given (non-uniform data structures and algorithms for different shapes).

Couple of notes:

- see how quickly you got from my attempt to talk about forces and reaction to forces to the usual talk about languages (du jour), features, the mechanics of things, etc :-)))

- people familiar with CS research will recognize that the shape problem is a variation on the well-known "* problem" (won't name it as I don't want to influence your creativity). The * problem has been widely investigated in the context of OO and FP languages and the consequences are well-known. There is also an active segment of PL research trying to improve OO and FP languages to deal with that. Often, lacking a theory of forces and materials, they come up with kludges, where the solution is actually quite simple in dynamic languages, and a minor addition (that I've already mentioned) to OO languages would be more than enough.

Look forward to see some code : )

@ramtop said...

Hi Carlo,

this is a very late comment, I intended to reply you soon but then I completely forgot about it and today when I was looking at some OOP stuff I read it again. Good as always. :)

Also a disclaimer: I'm not interested here in proving FP better of OOP or anything like that.

I liked your post but I'm not convinced that OOP minimize the artifact changes for a change of "decision".
I'm convinced that some problems are easier to solve from a OOP point of view and other from a FP p.o.v.

Now if I understand correctly the problem we have a list of shapes and we want to space them evenly on the plane.
We decided we want each shape to give us a bounding box and then to space them horizontally.

Now in 15' of Clojure I cannot finish the exercise but I copy here the code to see how I would solve it.

For each shape let's define a function that take a variable number of arguments plus a reference point, and give us back a bounding box, defined as a map with 4 keys.
For example for a circle we need only ray + center as parameters, for a rectangle (parallel to our plane) the two sides and one vertex. At same way we can create other shapes, as far as we can calculate the BB.

Once defined the functions I can put my "objects" in a list currying them with only size parameters but avoiding the last parameter (position).

Finally I can create a function that take the list, with a map determine the BB for position (0,0) and then calculate all the BB widths.

With that I can calculate the origin offsets and finally space them for an evenly disposition.

At this point if I want to add a new shape I just need to calculate the function of the new BB, no need to change any part of my code.

Since the solution is pretty simple I'm sure I'm breaking some of your rules. :)

Still my experience with FP so far is that I don't need to change my functions if I change requirements more often than with OOP.

Actually in Scala I find quite useful to mix FP and OOP for optimal results.


------------

;define some points and the origin
(def p0 {:x 0 :y 0})

(def p1 {:x 10 :y 12})

(def p2 {:x 7 :y 2})

;define 2 shapes
(defn circleBB [ ray center] (hash-map
:x1 (- (:x center) ray )
:x2 (+ (:x center) ray )
:y1 (- (:y center) ray )
:y2 (+ (:y center) ray )
))

(defn rectangleBB [ x-side y-side origin] (hash-map
:x1 (:x origin)
:x2 (+ (:x origin) x-side )
:y1 (:y origin)
:y2 (+ (:y origin) y-side )
))


;example of bounding box
;(circleBB 3 p1)


;define some "floating" shapes (curried)

(def smallcircle (partial circleBB 2))

(def bigcircle (partial circleBB 10))

(def smallsquare (partial rectangleBB 5 5))

;(bigcircle p1)

;(smallcircle p2)

;(smallsquare p1)


;define the list of shapes
(def shapelist [bigcircle smallcircle smallsquare])

;calculate even distance in a space width
(defn even-distance [spacewidth shapewidths] (/ (- spacewidth (reduce + shapewidths) ) (count shapewidths)))


;calculate list of BB, widths and offsets
(def bblist (map #(% p0) shapelist ))

(def bbwidths (map #(- (:x2 %) (:x1 %)) bblist))


(def bboffsets (map #(/ (+ (:x2 %) (:x1 %)) 2) bblist))


;(even-distance 100 bbwidths)

;calculate the even spaced origins
(defn spaced-origin [offsets widths distance] (map + offsets
(range 0 distance
(even-distance distance widths))))


(spaced-origin bboffsets bbwidths 100)

Carlo Pescio said...

[Part 1]

Uberto,
while you were busy :-), I decided to shut down this blog (http://www.carlopescio.com/2013/03/game-over.html), but your comment is a good opportunity to make the point of this post clearer, so I’ll indulge in my usual, longish, multi-part answer. Normally, I would start with a rant : ) about the “real message” in my post, but let’s start by looking at your code instead. I think it would help to understand the real reference case, because if you conflate everything into a short snippet, you lose sight of the real issues. The scenario should be really familiar, just think of something like powerpoint or a vector drawing program etc:
a) You can draw shapes of different types; shapes are positioned when you draw them
b) You can then select a number of heterogeneous shapes
c) You can do a bunch of things with those shapes: rotate, stretch, and… distribute horizontally.
Now let’s go back to your code. There are quite a few issues in that code (sorry :-) but I’m going to cover only those which are relevant to this discussion.

1) You’re not creating shapes [see (a) above]. You’re creating bounding boxes from the very beginning. Your “bigcircle” is a (curried) bounding box, not a shape.
This, of course, is cheating. Well, it’s cheating twice, as we’ll shortly see. You don’t know from the very beginning what you’re going to do with your shapes, and a bounding box won’t help (or won’t be enough) for other cases. So, if you had received a list of positioned shapes (as you would have in a realistic case), you would have had to build a set of bounding boxes first. How? By switching on shape type? There you have your non-extendible point.

2) Let’s ignore that for a moment. I hope you understand that you’re just building a degenerate object there. You’re putting together data and 1 function. You can use currying only because it’s just one function, otherwise you would have built the usual pseudo-object thing.

3) In fact, it’s slightly worse than that. You forced upon your shapes the idea that they should be 0-centered, and that the center will be provided later. But this is very unnatural for some shapes, like an irregular polygon, which are best described by a list of (positioned) vertexes. Why did you do so? Because then you don’t need a polymorphic Move function (I hinted at that in my post). That allows you to use currying instead of building a more obvious pseudo-object with two functions inside. So you force a uniform representation over shapes, for the benefits of *one* thing you can do with them (distribute evenly). That does not scale to the other gazillion things you have to do with shapes.

Carlo Pescio said...

[Part 2]
I’ll stop here, as there is no point going further. The real point is:
a) You’re still mimicking objects
b) You’re doing it wrong
c) Try to fix it, and you’ll be even more object-like

Of course, there are other constructs in clojure that would help you with this task. Which brings me to what this post was really about.


The post was not about “objects are good” or “functions are bad”. But first and foremost, it was not about objects as you keep thinking of them. From your comments, it’s rather obvious that you keep thinking that “OO is that thing you do with objects and classes”, while my point was: forget that, here is a definition of an OOS that does not depend on objects and classes, but on forces and reactions. And yeah, objects and classes help realizing an OOS, but other things would help as well, and you can of course implement an OOS in a functional language by mimicking objects, classes, and those other things (but not simply by creating functions).

Oh, by the way, I learnt the functional paradigm *before* the OO paradigm, right where monads were invented. I’m not writing this stuff out of conservatory ignorance. If anything, I’m surprised by how many lessons the new wave of functional programmers seem to have forgotten (like: information hiding is still important).

I’m way beyond the point where one needs to see things as “good” or “bad”. I see languages and paradigms as materials, which reacts in a different way to forces. My interest is in understanding those forces and those reactions better. That’s what I call the physics of software. Debating over paradigms is as unfruitful as debating on wood over steel, which reminds me of another longish answer I wrote quite some time ago: http://www.carlopescio.com/2011/04/your-coding-conventions-are-hurting-you.html#c88334685485118639 : )

Unknown said...

Your mention of "centers" and form and function remind me of Christopher Alexander, or perhaps Jim Coplien's interpretation of C.A.'s with. Is this a coincidence?

If so, then you should watch the following two videos and tell me if you find relevance :)

https://youtu.be/98LdFA-_zfA

https://youtu.be/Fhmm2Ld9VqQ

Unknown said...

Your mention of "centers" and form and function remind me of Christopher Alexander, or perhaps Jim Coplien's interpretation of C.A.'s with. Is this a coincidence?

If so, then you should watch the following two videos and tell me if you find relevance :)

https://youtu.be/98LdFA-_zfA

https://youtu.be/Fhmm2Ld9VqQ

Carlo Pescio said...

yes, as it says in the end this post came after other 17, where I've copiously quoted many ideas from Alexander (from the early Notes on the Synthesis of Form to the more recent Nature of Order). I also quoted Coplien when relevant. I just stopped referring to Alexander whenever I mentioned a center at some point, as these post really had a small / stable niche of readers, and they sort of knew the background already.

This work evolved into what I call "the physics of software"; you can find an historical perspective (with Alexander and Coplien referenced in context, plus others of course) here: http://www.physicsofsoftware.com/the-story-so-far.html