Rethinking the object-oriented paradigm - what can we throw away?

Hi sph! Your questions here seem almost exactly like those I have about OO. Some comments:

Data cannot have “bugs”. Data is a static representation of facts. It is truthful by nature.

I think… I wouldn’t quite go so far as to say that “data is truthful”, but I also think I understand what you’re saying.

I’d say rather that data is finite and static and self-contained by nature. While “code” (functions, algorithms, objects) creates or infers structures/spaces that are infinite (potentially uncountably infinite, in nasty and paradoxical ways) when run; can potentially self-modify even when just “read” (yikes!!!); and also has lots of dependencies including to specific hardware. It is much, much harder to make code safely transportable between different types of computing machinery, as opposed to data (in the form of sequences of symbols) that can live for decades or centuries.

Clay tablets from Sumeria and rock inscriptions from Egypt are “data” that we can still process today, given a fairly thin layer of symbolic encoding (a character set like Unicode, say). Interpreting that data can be much harder. But we can at least uncover them, dust them, photograph them, sketch them, digitize them… because they are “dumb” physical objects that exist in one simple form and don’t change when we “read” them.

But what if our records from ancient societies, instead of clay tablets and scrolls, were all “objects” - complex calculating machines like the Antikythera mechanism, broken after centuries of disrepair - or worse, centuries-old iPads - and we had to successfully “run” them without error in order to access the data inside? I think the task of archeologists would be immensely harder.

And that’s just the task of “transmitting information across time”. I think transmitting information between dissimilar computing platforms is similarly complex, and we tend to find the same level of problems. Including needing to be an “archeologist reassembling the Antikythera Mechanism” just to find out what, say, Smalltalk-72 software was like to run on a Xerox Alto. And that’s for very well documented systems for which we have working hardware and the designers are still living. Successfully transporting code across systems gets much harder from there, and that’s why OS and platform lock-in is a thing.

Here we define a Pen as a structure, and create a set of methods (function that have an implicit self argument) that are able to operate on it. A user is always able to create new “verbs”, i.e. to expand the model through which we interface with data. But also a user is free from information hiding, so they might create a better implementation of Pen, which will be 100% compatible with other Pens, which nothing are than a struct. Additionally, Pen is easily serializable, can be transmitted onto wire because it’s nothing else than a bag of bytes.

Yes, this idea has some merit to it I think. It’s abandoning the ideal of “encapsulation” and instead reducing an “object” to something a lot simpler: “a memory protected namespace”. (I assume the implementation of “struct” here would prevent C- or Forth-style random access to memory via arbitrary integer pointers - that’s not nothing!)

Javascript pretty much gives us this. Javascript is very large now, and I wish it were a lot smaller. (Lua is almost that smaller Javascript, but has some nasty edges to it that give me pause).

The syntax I’m exploring here is OO without inheritance and encapsulation.

Alan Kay I think would agree that OO should not have inheritance, but he would disagree about encapsulation. But I’m on the verge of thinking, as you do, that encapsulation itself is also a problem and not a solution when we’re dealing with large distributed systems. It’s all well and good to say “just send messages between computers”, but to actually send messages in the actually-existing Internet, we have to send messages not as Turing-complete computers but as dumb, strictly-defined data formats. And we’re also finding that data is even better if it’s not just dumb but also immutable (it parallelises well and it lets us save and restore histories, and we can save a lot of unnecessary computing power if we let our transmission packets simply be our storage records and vice-versa).

So if we were to make local desktop computing more like the actually-existing Internet of 2024 (the good/working parts, that is) and not a theoretical 1960s idea of an Internet yet-to-be-built… we should probably think more in terms of immutable data records with clearly defined, system-independent formats, and less in terms of “little virtual self-modifying computers which can do literally anything they want with messages” - much less “send an infinite regress of little virtual self-modifying computers between other virtual self-modifying computers until it’s all a completely unpredictable Turing-complete mess”.

I dunno. I’m very torn. Encapsulation and information-hiding sometimes is helpful: it might even be required, in small and carefully controlled doses, for security. But in many distributed contexts, I feel like unlimited encapsulation can be really, really unhelpful and even extremely dangerous.

At the very least, enscapsulation has to be accompanied by a clearly defined, system-managed serialization/deserialization protocol so that our local systems can examine remote objects (up to and including all the things we do today when we “compile source code”) in order to decide if they’re safe and trustworthy or not.

I keep thinking: what if our fundamental data type was “an immutable record, accompanied by a function pointer (very similar to an object constructor), where the core system/langage kernel makes a very strong assertion EITHER that the associated function has BEEN run on this record and returned True, OR that it can be logically proven that the function IF run WOULD return True”? How far would this get us? This would give us similar data type/shape guarantees to objects, but without either inheritance or encapsulation. Would also be very much like a type system except that the type system is just a function (but perhaps doing the “IF this were run THEN…” inference would require a type system).

3 Likes

Re “this rebuttal by the author of REBOL” (The problem with OOL is not the OO. )

A pencil was used to write and draw. We thought, “Wow, there’s a pattern, and it seems to be quite natural.”

However, it was a false model. A pen does not write and draw, it takes a human to make a pen write and draw. The actions of write and draw do not belong to the pen. OOL is not a complete solution. Too many of the behaviors of objects come from (or are influenced by) sources that are external to their encapsulated definitions.

Yes, I agree with this. There’s something quite attractive yet deceptive about the OOP idea of tying data to “behaviour”, and I think Carl has put his finger on the problem here. “The behaviours are external to their encapsulated definitions”. Yes. That’s the problem I keep striking with OOP. It puts behaviours in the wrong place. Behaviour is extremely specific to hardware or platform; it doesn’t port, while “dumb data” does.

And I first struck this problem back in the 1990s-2000s in the Interactive Fiction scene: there were several Object-Oriented IF languages, which came with elaborate world-simulation libraries, and of course these libraries never did quite what any one writer wanted in their game. They had lots of behaviour! Much of it unwanted behaviour. But because of encapsulation you couldn’t change this behaviour. And so overriding the OOP to do what you actually wanted became a huge problem. I’m pretty convinced that we tested the OOP model to destruction in that community.

So much so that Graham Nelson with Inform 7 (2006) moved entirely away from the object paradigm towards a complicated steampunk mashup of custom whitespace-significant syntax, quasi-logic databases, rules systems, types, and imperative procedures… it’s a strange, strange language, but it exists because OOP had clearly failed the game-development community as a paradigm. And this is super weird because Interactive Fiction came from the objects community! Not quite from Alan Kay’s Xerox PARC but from the 1970s MIT branch of the ARPA AI community; and specifically from the MDL language, a somewhat object-oriented variant of Lisp. We weren’t actually using MDL, as it was a “lost” language at that point. But it was the peak of 1990s object fever, and yet objects as an organizing principle failed hard in the very environment (interactive simulation, like Simula) for which they were originally designed.

That was 2006, and it’s when I first started to wonder what was going on with OOP.

1 Like

This is a very good question, and I think I have ONE answer.

See, I’ve been trying to develop my own personal assistant, todo list, note taking software, for reasons. As much as I am comfortable with functional programming (and I adore it compared to current OOP languages), as much as I natively grok the idea that functional programming = data transformation, the problem is that is not how I and most people think.

When I think of writing a todo list, I don’t think it terms of data transformation. I don’t natively visualise the transformation of a text file containing a list of strings into an HTML page with checkboxes, even though this is the best way to model the problem.

What I think of are things. Objects if you will.

There is a Page, it has a List of Todos. Each Todo is a Box with a Checkbox and an Input field.

Let’s go to another abstraction level. What can I do with this application? There is a TodoProgram which I can operate by:

  • listing all todos
  • entering a new todo
  • mark a todo as complete

Sounds very much like the definition of an object, and the messages you can send to it. This is how we view the world. We pattern match signals, and give them names. We generalise and categorize.


The fact that programmers are considered magicians by laymen, or scribes, is because programming is completely unnatural. I argue that’s a bad thing and we’re operating at the incorrect abstraction level.

Civilization exploded and grew exponentially every time there was a break through in communication between its members: first with spoken language, then written language. The advances of the past thousand years are because EVERYBODY, not just scribes, know how to read or write, so the potential pool of ideas and thinkers grew as large as humanity itself. There is almost a 1:1 equivalent between thought and language, so it’s natural and an actual “bicycle for the mind”. We don’t spend much mental power thinking of the shape of letters, the colour of the ink, whether long words are better than short words.

On the other hand, programming is still very unnatural, we spend a lot of time thinking about programming itself, how to program, how best to structure code, as if the end result of programming is just a side effect.

In short, a better programming paradigm is one that is closer to how we think. As much as I love FP, human thought processes have nothing to do with data transformation, so it is ultimately an evolutionary dead end.

2 Likes

I get what you mean but… “immutable data records with clearly defined, system-independent formats” is what we have already today. Our NICs send immutable data records encoded by system-independent formats (Ethernet, TCP/IP, HTML, etc.)

I don’t see how the status quo that exists today can also represents the future or a supposed paradigm shift.

More standards or more data-encoding formats won’t make programming or communication between programs any easier.

1 Like

Accessibility of programming and simplicity of software are indeed good things to aim for.

You make an example of something that seems like a GUI application, which I think is already a type of software that generally benefits from OOP, at the level of graphical components and their interactions. And, it makes sense, since if our goal is to make software accessible, we would probably make lots and lots of GUI applications. They are much easier to use than text-based applications after all.

And yet, there are many real-world problems that are not GUI applications, but that we want to solve using digital devices. For example, steering systems, present in everything from thermostats to robot lawnmowers to cars (not just self-driving ones).

Or why not inventory management systems? Sure, they normally use GUI interfaces at the very outmost part of the software, closest to the users, but their value is in their ability to synchronize data across many different devices and quickly calculate accurate summaries and statistics of the data that they manage.

These problems, such as how one terminal in one storage facility should synchronize an updated state with all remaining terminals, are more difficult to immediately imagine as a human. You’d need to start jotting down the information, using pen and paper perhaps, creating examples of data that is received by your software in a specific real-world case and what your software should do with it. Regardless of whether you choose to use object-oriented programming or functional programming, the problem definition is separate: it’s about the interfaces that you use to receive data, and the interfaces you use to output it to some other location.

I don’t want my definition of ‘problems’ as data transformations to be misinterpreted: I do not believe that functional programming languages are superior to object-oriented languages, or even imperative ones. My definition stems from the fact that as software developers, the product of our work will sooner or later be executed on a processor, however many platform layers that lay between it and the code that we write. A processor does nothing but transform data.

The only version of the code that accurately represents the real, actual problem that we are solving is the most low-level version of it: the machine code. And, all programming languages exist to eventually map to actual machine code, no matter how many convoluted steps it takes to get there. I think you can notice this in the design of programming languages. For example, function pointers. In machine code, function pointers can be made possible by a jump instruction that takes an address from a register and jumps to that location. C and C++ have a version of this that is conceptually very close to the machine code version. In Java, the corresponding effect can be achieved using interfaces.

You might argue that this makes Java different because it uses polymorphic features to ‘emulate’ function pointers, and in a way you’d be right. But if we think more deeply about how Java (and C++, for that matter) implements polymorphism, we’ll realize it’s all done using regular old data structures and functions. Thus, complexity is increased, but the same problem is solved.

Looking up how C++ solves polymorphism in practice is very enlightening and may lay bare to some what the benefits of this feature really are, and what they aren’t. I recommend this explanation: LearnCpp: The virtual table. Inheritance, for the record, can be simply understood as the concatenation of data structures coupled with virtual function tables.

I don’t think it’s wrong that we should try to make programs understandable and easy for humans to write according to their understanding of the problem. But I do think that the traditional object-oriented theory of a “Kingdom of Nouns” has been overapplied. A GUI application is an application where large amounts of state must be consistently kept and changed only slightly, from irregular entry points, which is a scenario where objects thrive, but a terrible scenario for functional programming. However, functional programming really shines in scenarios like when a steering system needs to repeatedly process complex input and translate it into control signals for, say, electrical engines. In this case, it makes more sense, to me at least, to describe this as an input-output function rather than interactions between objects, and I think that means that in this case, functional programming is in fact more intuitive than an object-oriented approach.

Thus, to make programming more accessible, I would rather focus on how to make the hardware more accessible, when solving different problems. That’s the only way to make sure that we aren’t hiding something through abstraction that may in fact be valuable to some solutions.

1 Like

Seems to me you have used nouns (steering systems, thermostats, cars, etc.) to exchange this piece of information with other humans like me, because that is indeed how we think.

I haven’t programmed a GUI in two decades, if you don’t count websites, and nouns are still king. My latest backend application, a links checker, is no exception. At least I was fortunate enough to write it in Elixir, where these nouns are processes that exchange messages with each other, which made modeling and debugging much easier than any alternative.

I think the true limitation lies not in the language, but our development tools. I can’t help but imagine how much better/easier if these objects/nouns were represented as a box on a “desktop” of some sort, that I can connect or query to see how they behave. GUI development, with RAD tools that somehow have gone the way of the dodo, realised this in the 90s.

2 Likes

Compilers and interpreters are tools as well, and their features, like polymorphism, inheritance, member methods, closures and generics, are in particular simply utilities. I like to understand the features of an imperative programming language by imagining what it would require of me to write equivalent functionality in C. It gives a good idea of exactly how much the feature helps me, which can quite often be surprisingly little, and I can then weigh that against the issues that the feature introduces.

C is useful to think about because it is minimal: it deals with data structures and functions. These are, to me, the two defining features of an imperative language. Inheritance, polymorphism, generics, closures and member methods, all of these language features can be reduced to the automation or generation of code that is, in the end, made up of data structures and functions. Yes, even in Java.

Thus, when talking about software design in an imperative language, I believe that there are only two primary things you ought to worry about: how to model your data structures, and how the functions that deal with those data structures should look like. From there, you can choose which programming language features you would like to use, or not use, for any particular problem.

Well, I’m a Java developer by profession, so I’m quite familiar with using nouns. What I’m referring to as the “Kingdom of Nouns” theory is the idea that nouns that seem like they have a taxonomical relationship ought to also be related in an inheritance hierarchy.

You can certainly program working programs even as you think in that manner. But it will impact the way you model your data structures. And whether your resulting data model is an accurate representation of the actual problem you need to solve, or not, will mostly be determined by luck. If you’re unlucky, and the data structures don’t represent the actual problem in a good way, you will notice it on the functions that attempt to deal with those data structures. They will be everything that we associate with “bad” code.

I think skilled programmers who are schooled in object-oriented programming know how important it is to model data in a good way as well, and have learned to use nouns without relying on the idea of a Kingdom of nouns. It was Michael Feathers who said that, if you do object-oriented programming right, it kind of starts to look a little bit like functional programming. And ironically, the Java tutorials themselves encourage the use of immutables.

I don’t know much about Elixir, unfortunately, but I’m sure it solves your particular problem well!

Yes! So we know that this technique scales extremely well - where we’re using it. But we’re not using it everywhere that we could.

A good question!

So what I’d say is that we’re not actually using immutable data records everywhere in the Internet/Web model, only in some layers. And the paradigm shift would be more of a case of evolving the model by replacing the layers which aren’t based on immutable data records. If I’m correct in my guess, we’ll find as we remove/replace the non-immutable layers that we immensely simplify our Net/Web stack- while also improving reliability/security and increasing the stack’s power.

For example, here’s what we’ve currently got:

Ethernet frames and IP datagrams: yep, these are essentially immutable data records. And they work well. But! They have a very short lifetime - milliseconds to seconds - and we throw them away, and repackaged them into completely different data formats, almost instantly. What if we didn’t throw them away, but rather aggregated them?

TCP streams - these aren’t immutable data records, at all. These are about recreating something like a point-to-point 1960s Telex or AUTODIN link ( Automatic Digital Network - Wikipedia ) over top of IP packets. TCP manages congestion control, which was not a simple thing, but many protocols especially in gaming have long ago thrown it away and do their own thing.

SMTP email: Yes, an email is a very good example of a very immutable data record. We route and store and forward and keep emails for years to decades. SMTP is terrible the way it doesn’t have any kind of encryption or message-authentication built in, but the concept here is very much “adding immutable messages to a store and then keeping them”.

HTTP - an HTTP request itself, like a SMTP request, is implemented over a transient TCP Telex-like stream. But a HTML “page” in the very original 1989 sense, yes, that’s an immutable data record. Well, sorta-kinda - the binding between the page and the page URL can mutate. So it’s not as immutable as an IP datagram or a SMTP email. But it can be slow to mutate, a page can live for decades. That’s why HTTP can cache, and why it can scale. But HTTP “PUT” and “POST” are actions, not immutable data, so they break the caching model. And CGI and everything that came after it - all the server-side processing mechanisms - are extremely not immutable data, they’re “remote procedure call” type mechanisms. Going back to the transient point-to-point stream concept: get a 1960s Telex link to a remote computer, say “hey specific named computer, do this for me,” and wait for the result.

The DOM and Javascript - those aren’t based on immutable data at all. The initial webpage download, and the caching in the web browser, is somewhat/sorta/not-quite immutable - but then the Javascript engine starts, and Javascript spins up a host of little OOP objects, all very mutable. But like IP datagrams, it doesn’t persist those objects, it deletes them once we close or reload the page. That’s both a huge waste of programmer effort, and also a massive semantic break in the expectations / mental model of the user experience. A source of immense and ongoing frustration.

Then on top of Javascript in the browser, there’s React and similar web frontend frameworks. And React et al have gone back to the immutable data idea! Sorta-kinda. Data that’s immutable in part, until it’s not. But since they’re running inside a very mutable OOP environment, they have to recreate immutability from scratch. Fortunately Javascript has the “Object.freeze()” mechanism, but there’s lots of wasted energy and reinvention of a lower layer here.

So we have multiple conflicting paradigms - immutable, mutable, immutable, mutable, immutable - sandwiched between each other. And the interface/conflict between all of these paradigms is chewing up massive amounts of our computing resources, causing us to reimplement everything multiple times, and creating bugs and security flaws at the interfaces because of what’s called the “impedance mismatch”. We talk a lot about the “object/relational” impedance mismatch - how to safely/correctly store objects into SQL, because they’re just wildly different paradigms - but the immutable/stream mismatch and the immutable/object mismatch are also huge.

So one possible paradigm shift would be to keep Ethernet and IP, but to replace TCP with something like “Content-Centric Networking”, packets with Posts, and the Telex-like point-to-point “Stream” concept with the concept of multipoint-to-multipoint pub/sub “Feed”. Feeds and Posts could have a very long lifetime - years to centuries. (After all, how old are books? We’re still reading ones from thousands of years ago.) So instead of generating Packets just to route across the internet and then almost instantly throwing them away, we’d generate and subscribe to Feeds, and keep Posts from those Feeds for as long as we wanted that information. Maybe forever.

(Imagine a Post being something like all of: an email; a Tweet; an edit to a Wikipedia page or a web forum like Malleable; a database transaction or record; a Git patch; an entire file; a node in a structured document; possibly, even, if we had an all-the-way-to-the-metal OS, a disk block. Like, say a large chunk of your hard disk was just an IPFS store or something like that, right? At least for all of the immutable data that you didn’t author yourself and wasn’t constantly changing.)

Then inside our “browser”-equivalent for Feeds and Posts, we’d have some programming system which possibly wasn’t little transient OOP objects mutating each other (as in Javascript), but instead jumped straight to something like React, but as a language: multiple Feeds and Posts inside your “browser session” - but perhaps not being deleted when you “close the page” (it wouldn’t be a page) but persisting forever, live, in your desktop.

That’s the sort of thing I’m thinking about. I’m absolutely not technically capable of realising it, because it’s a monster of a vision and the world is very large and full of moving parts. But it’s a sketch of where I’d like to see us get to. It might take a hundred years at this rate, with all our technical debt from competing paradigms, but at least we should try to have a vision. If nothing else, it’s a hobby.

1 Like

Yes, I agree. It is a very human and intuitive way of thinking to ascribe nouns and categories to things. This is one area where some forms of OOP shine. It’s also where Type Theories shine - they also like to describe nouns and put those nouns into categories (Types rather than Classes).

Oddly enough, though, this usage of “Classes” (Page, List, Todo, Box etc) seems to be the part of modern OOP that Alan Kay dislikes the most! And he dislikes the idea of “Abstract Data Type” even more than he dislikes the idea of Class. In his vision of “real OOP” he seems to want there to be nouns, and messages sent between nouns that cause actions/behaviour… but he doesn’t necessarily want the nouns to all by described by an “IS-A” relationship to a Class hierarchy, much less an “IS-A” relationship to a Type hierarchy. So that’s quite confusing.

I can’t say that I entirely understand yet what it is that Alan Kay wants that isn’t Types or Classes, but is Objects. But one observation I would make:

Let’s assume that the Object view is right, and everything in a software system is some kind of object/thing/noun. That seems a fairly uncontroversial idea to start with.

So then, an idea or assertion or condition or datum about the nouns in our software system – like “This noun is a Todo” or “A Todo has a Checkbox” or “This Checkbox is ticked” – must be a noun itself. What kind of noun, then, is it?

To my way of thinking, this is a very important question which is not often followed up. I believe things along the line of “This X is a Y” to be something like a logical assertion or a relationship. A group or set of these things would make a relation. Looked at a certain way, a relation, perhaps with a meta-assertion of “is-a Class”, looks very like an object. A certain subset of relations (those which map several things to only one thing) are called functions.

We have an entire theory of computation based on functions. But relations are wider than functions, and include things like type and class definitions, and just plain data, as in databases and file systems, and lines of source code itself.

So I feel like if we really want to get to the bottom of things, if we poke at “objects” we find that they are not actually simple things but are complex systems made of simpler things: logical assertions/relationships. The “Object is-a Class” relationship/assertion is one kind of relationship, “DataStructure is-a Type” is another kind, “Key has Value” is another kind, “Function-of-Argument maps-to Result” is another kind, “DataRecord is-in Table/File” is another kind.

So for a long while I’ve wondered how far we could perhaps get by focusing on relations as the building blocks, with objects, classes, types and functions as sets of relations wired together. This is why my interest in Prolog and Kanren. Or rather, it came about because of my experience playing with Prolog as far back as the 1980s, and how easy it looked to be to define object-oriented systems in terms of Prolog assertions - but how difficult it was to go back the other way and try to define (very small) Prolog assertions in terms of (much larger, more complicated, with behaviour attached) object-oriented systems.

Prolog itself isn’t the answer, because it’s a circa 1970 language with circa 1970 assumptions baked into it. But something in the “relation” space feels like it’s a very large missing piece in our software thinking so far.

I read him saying that what he’s interested in is the messaging part. Objects are just one way to do that.

1 Like

My observation on the general idea of formal logics as applied in software is that it tends to be incredibly high level, and often not in a way which is easily amenable to optimizations.

The biggest success story I can think of is SQL. Prolog/Datalog/Kanren have plenty of applications, albeit narrower in focus. Type checkers are, in a formal sense, analogous to theorem provers, and they’re conceptually similar to logic programming languages. There’s the whole field of formal verification and its tools, e.g. proof assistants, model checkers, SMT solvers. Constraint-based layout is another example in UI and has popular implementations on web/mobile. (These are all I’m recalling off the top of my head, but I’m sure there are plenty I’ve forgotten or never heard of. There’s also all sorts of borderline cases like CAS solvers, optimizers [numerical], optimizers [compilers], etc.)

IMO, these examples all fit the theme of almost maximally declarative programming, but with several no-free-lunch caveats attached. They cannot promise performance and correctness/completeness, in whatever form that takes. Just to achieve relatively good performance in a wide variety of situations tends to require monstrously complex implementations. And then there’s also the issue of expressiveness - at maximum generality, translating logical properties into mechanically verifiable statements is Hard and Tedious.

1 Like