A lot of the recent trend towards OOP-bashing comes from bad implementations of OOP, just as bashing design patterns is more to do with badly thought out architectures and overuse of those patterns.
In fact, the origins of OOP are basically what we would now call microservice architecture (CSP-inspired languages like Go being a specialisation of this). Each service can be as stateless or as stateful as it needs to be (without shared state) and services should be loosely coupled.
That's quite different to most large-scale OOP architectures, but it is possible to code in that style in any OOP language.
Heavy reliance on inheritance for code reuse is a whole problem class in itself that again has little to do with OOP and lots to do with the influence of C++.
>A lot of the recent trend towards OOP-bashing comes from bad implementations of OOP, just as bashing design patterns is more to do with badly thought out architectures and overuse of those patterns.
sorry, but this is no true Scotsman fallacy. All these "bad implementations" is OOP, real OOP, not some idealized ephemeral non-existent Avalon. If an idea easily and more frequently than not lands itself to the bad implementations then there is something wrong with the idea. (I happen to come from a country which was pursuing what was looking like a pretty good idea - communism - which has got a lot of bashing during recent decades, due to "bad implementations" i guess, and as result it was critically rethought and now we know what it is not a good idea really)
In sports, training coaches sometimes forbid to use some play element. In OOP classes there should be an assignment to design a system without inheritance. Just to show what is most important in OOP.
I was taught OOP using Modula-2 in 1989, by defining structures and functions that took a pointer to the struct as first parameter. No inheritance goodies! I used that style in C until 1996 when I definitely moved to C++. Probably why I am not terribly dismissive of OOP but very much of things like inheritance trees, the Java OOP style, and the overuse of GoF in architecture, literature and especially programming discussions.
Person HAS A IEmploymentRelationship
ContractEmployment IMPLEMENTS IEmploymentRelationship
FullTimeEmployment IMPLEMENTS IEmploymentRelationship
FullTimeEmployment HAS A IDirectReport
Manager HAS A Person
Manager IMPLEMENTS IDirectReport
And then salary() is a polymorphic function declared by the IEmploymentRelationship interface, where FullTimeEmployment instances use the manager somehow, and ContractEmployment doesn't.
You can even put some sugar on that by having a salary() function on Person that calls the salary() function on its IEmploymentRelationship—but I wouldn't, for the same reason I wouldn't denormalize a relational database.
If we redefine the word "OOP:object" to mean process-with-mailbox, and redefine the word "OOP:message" to mean asynchronous message passing, then objects and messages make sense and are useful.
Those are not Smalltalk's definitions -- even though I think you're right on about Alan Kay's original inspiration -- and confusion can arise if we redefine terms.
Rather the opposite. Almost all HPC (high-performance computing) systems use message passing and not shared memory. The fastest and imho best unix-like kernel L4 is entirely MP based.
So if you want to embrace high performance (and concurrent), go message passing.
Alan Kay's favor of late binding does not help performance, as method lookup at run-time dominates the cost then, but the fast systems are early bound, i.e. typed.
Well, you are discounting CUDA, which has been seen as suited for many tasks when compared to MPI. Of course, you can use MPI and CUDA together (people are working on that), which you then have messaging between nodes and shared memory (via GPU-style SIMD) within.
Of course, you are probably referring to NUMA, but people still actually use that.
Pure OO isn't very much desired in the HPC world, but then neither is pure FP.
I think it's a bit charitable to call what Objective C does to be "message passing". The messages are not asynchronous and have only a single recipient, so in practice I would say they're method calls with late binding.
However, they can easily be made asynchronous and address multiple recipients. See Higher Order Messaging[1][2], first implemented in Objective-C[3]. So for example:
The reason this is easy in Objective-C is because its brand of message passing is just (barely) powerful enough to qualify as message passing. In fact, if you squint just a little, you can see it as fully reified message passing with the common-case (a synchronous method is invoked) optimized.
At least you can redirect messages to another recipient, and write a handler for "unhandled messages." Impossible with c++, at least with standard method calls.
"OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP. There are possibly other systems in which this is possible, but I'm not aware of them."
Such a pity that this filtered essence about OOP was lost on me during formative years of learning OO-programming; for no fault of mine! It took me half a decade into professional programming in Java to realize the importance of these key concepts.
And the worse part is that most of the books/blogs start OO programming tutorial with examples that try to literally model the problem state using Objects (e.g., "Animals", "Shapes" etc.,).
I guess tutorials, classes just focus on these key goals and show how concepts such as "polymorphism", "inheritance" etc., are work towards achieving (or not) them. What generally happens is that one is taught about all these peripheral concepts and students are left to wonder the problems they are trying to solve.
Those examples worked really badly for me too. It wasn't until about a year after I got my first paycheck for writing code that I made this switch in my mind. My thought process started out something along the lines of "ok, i just need to get all these things in line... somehow... and then I can accomplish the goal". However at some point I realized, I did some similar things quite often, and I needed to make some piece of code kind of generic. My mindset kind of changed dramatically. Instead of thinking about how I could make something work, I started to think of how I'd like the API to look. How could I make the code reusable. I thought about the perspective of another dev using the code rather than the goal itself, since that eventually became the easy part.
Once you start thinking in terms of the API, and how you can design it so someone with minimal documentation could still use it (though you still document it!) you start wanting to hide the implementation etc, and all the other OOP ideas fall into place.
of course, I don't think it's something that can be taught. It's an evolution. When you first start programming, everything is unknown, so you don't have the brain power to focus on design. When you learn more, and you free up brain power you can start concentrating on these other things.
But just to show
how stubbornly an idea can hang on, all through the seventies and
eighties, there were many people who tried to get by with "Remote
Procedure Call" instead of thinking about objects and messages. Sic
transit gloria mundi.
Can somebody explain to me what distinction he's drawing here? What's the issue with RPC that's solved by Objects+Messages?
A remote procedure call is analogous to an ordinary function call, extended to a remote machine. It is static, perhaps even specifying the code to be executed, and therefore limited. But objects can be understood as independent agents (Kay calls them "real computers"), where a message is a request for some behavior. How the object is to implement that behavior is not specified in the message.
Kay gives the example of the Internet itself. A GET request for a URI used to be understood as an RPC: download the file at this path. But today GET requests are routinely abstracted. There is no literal directory /r/programming on reddit. Instead path components are better understood as parameters, i.e. arguments in a message send! And this lets them do anything they want, be implemented in any manner at all.
Unfortunately, this is one directional. Your browser makes an abstract request, but the server replies with literal code (e.g. JavaScript) that it wants executed on its behalf. The browser is sending messages to the server, but the server is making RPC calls to your browser.
This gives some insight into the relative stagnation of the client-side, compared to the explosion of server-side. Kay wrote that, if you communicate via messages, then "each object could be implemented using the programming language most appropriate for its intrinsic nature." We have that on the server, but are a million miles away from that with clients.
CSS (or more generally the dream of content and presentation separated HTML) might be considered message passing since the browser usually has some freedom with regards to on screen layout (implementation), although in practice many sites specify the message so precisely that only one implementation is possible.
That might be not what Alan Kay meant, but here's one description of fundamental problems with RPC: [1]
[...] the fundamental problem is
that RPC tries to make a distributed invocation look like a local one.
This can't work because the failure modes in distributed systems are
quite different from those in local systems, so you find yourself
having to introduce more and more infrastructure that tries to hide
all the hard details and problems that lurk beneath. That's how we got
Apollo NCS and Sun RPC and DCE and CORBA and DSOM and DCOM and EJB and
SOAP and JAX-RPC, to name a few off the top of my head, each better
than what came before in some ways but worse in other ways, especially
footprint and complexity. But it's all for naught because no amount of
infrastructure can ever hide those problems of distribution. Network
partitions are real, timeouts are real, remote host and service
crashes are real, the need for piecemeal system upgrade and handling
version differences between systems is real, etc. [...]
I wish you hadn't asked this question b/c it sent me on a wild goose chase, mainly as I tried to reconcile my understanding of OOP vs. Kay's version of it, of which I'm still unclear. I perused the [1] he mentions to try and get better insight. (My version is much more born out of my history w/ C++/Java.)
Kay says,
OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.
I read this to simply mean encapsulation of state + dynamic typing. RPC as I understand it is making a method call from one process to another, which makes no claim on how state and/or data should be handled and modeled.
So I'm with you. Is Kay just arguing here for a specific way of thinking and talking about computing? Or are there some more concrete/tangible computing rules implied by what he's saying?
The concrete rules I'd give you are in http://en.wikipedia.org/wiki/Object-capability_model -- note that inabilities are important, not just abilities. I'm a great fan of the thesis that's the first reference, which also gets into distributing the model over the network.
In a Smalltalk-style message passing system, each object has its own little parser. When you send it a message, the object will parse and interpret it however it wishes. Essentially, you send the object a sequence of tokens, it parses the tokens and generates some executable code that it then invokes.
In C# (or Java), it would look something like this:
interface Message : IEnumerable<Token>
{
}
public class Foo
{
public void Consider(Object sender, Message msg)
{
//possibly validate sender...
Executable result = this.Parse(msg);
if(result.IsValid)
sender.Consider(result.Execute(this));
else
sender.Consider(new MethodMissing());
}
private Executable Parse(msg)
{
//the ability to parse the message depends on the state of this object
}
}
The basic idea is that the object is always in control of what it does. Methods are extremely late-bound because the code for the method may not even exist until the object first creates it in response to a received message.
But, the big idea is that an object is just a little computer, and in a computer system, a computer is the smallest thing that you want to recapitulate.
Sounds more like you're describing Smalltalk 72 (http://worrydream.com/EarlyHistoryOfSmalltalk/#p12) than Smalltalk 80. I've written a fair bit of Smalltalk code, and I can't remember seeing something like what you're describing here. It would certainly be possible, but I don't think it's an accurate description of how most Smalltalk code works.
A remote procedure call blocks on the response, ensuring the system is only as strong as its weakest link. If every object communicates asynchronously with every other object, using messages instead of shared memory (hence avoiding locking issues), the design by necessity evolves in ways which becomes more robust and easy to scale. Synchronous RPC calls encourage unscalable and unreliable architectures.
I understand this passage as a comment on philosophical approach.
You can see the same interaction as a "Remote Procedure Call" or as "sending a message to an object".
Kay also said later that (anything lost in translation is my fault) he thinks he misjudged the scale of an object, instead of being tiny packs of methods, they should be interfaces for larger "computing devices". Like tiny virtual machines that can live into and move from actual hardware, and computing through communication over any kind of channels (local, remote).
Ability to change or replace object instances (maybe with instances of different types) on the fly could be an example. In general, you can design incredibly dynamic and reconfigurable systems. Whether you want to or not, and at what level should you stop doing it, is certainly a good debate to have.
I wonder what he thinks of Erlang, as it's very much about message passing, hiding of process state, and late binding. Of course, this is built at a higher level and it's not turtles all the way down: you don't send messages to numbers to do basic math.
Algebra in this context means a system of rules for deciding that two programs (or functions) that are expressed differently, are actually equivalent in the sense that they map equal inputs to equal outputs. I.e. f(x) and g(x) are equivalent if they return the same value for all x.
The algebra of arithmetic is the most familiar, and it lets us determine that f(x) = 1 + (1 + (1 + x)) is equivalent to g(x) = ((1 + 1) + 1) + x for any integer x. In this case, the relevant algebraic law is associativity.
One abstract way to think about integers is as sets of equivalent arithmetic trees, so "3" is a stand in for the set {(1 + 1) + 1, 1 + (1 + 1)}.
Other kinds of objects that can appear in a program can have similar kinds of equivalences under various operations.
As a less familiar example, take axis aligned bounding boxes. Translation and scaling both map an axis aligned bounding box to another axis aligned bounding box. Intersection maps two bounding boxes to one smaller bounding box. "Convex closure" maps two bounding boxes to a larger bounding box--specifically, the smallest bounding box that encloses both of them.
There are then various algebraic laws that let us determine that differently expressed combinations of these operations are in fact equivalent.
If "∧" represents intersection, and A, B, and C are bounding boxes, then f(A, B, C) = (A ∧ B) ∧ C and g(A, B, C) = A ∧ (B ∧ C) are equivalent because intersection is associative.
If we apply the same translation to two bounding boxes and then form the intersection of the results, this is equivalent to intersecting the boxes, and then translating the results.
There are also interesting equivalences that relate intersection and closure.
This kind of broadly construed "algebraic" manipulation can be used to rewrite one program into an equivalent program that executes more efficiently, and this is one way of thinking about what an optimizing compiler is doing when it optimizes a program.
For example an automobile can be thought of as an emitter of pollution, if your problem concerns pollution, or, it can be thought of as a particle, if your problem concerns traffic congestion.
The auto "fits" into each of these different problem domains.
Algebra is the study of sets and their operations.
"High school algebra" is usually limited to the study of real numbers and the operations of addition and multiplication.
In upper division undergraduate mathematics, you learn about other kind of algebras. For example, there is the set of regular polygons with operations such as rotation and reflection.
In OO, a class of objects defines the operations (methods) that are valid, and thus, a class is an algebra, and a set of classes is a family of algebras. I suppose that you would need a form of multiple-inheritance in order to have a family of algebras for an object.
Thanks. I still find it fascinating people do good work without fully understanding an area. It's as if you're not supposed to. I wonder if this could be used as a guiding principle in making things.
If what you are working on originally looks understandable, you should be alarmed.
I presume by 'understanding an area' you mean being familiar with established abstract models of that area? I think that often in practical conditions the abstract models are not the area under work. They are the mapping of the area to some formal system. And that the 'area' under question - whatever it may be - can be mapped to an infinite amount of formal systems. For lots of practical purposes, there is some 'obvious' algebra of a thing one wants to do - in these instances all that is needed is an individual who can map the problem to any logically coherent structure, even just a private one in their head, and a willingness to solve the problem - and great stuff can ne done without a literature review. Sometimes the establishes models are really, really good since they either simplify a messy looking problem and/or point out to some not so obvious aspects of a system at which point an expert in the field can have an advantage over the clever layman. I think sometimes the knowledge of existing formal models can make a person blind to certain facets of the system they are working with.
Also, if one approaches a thing by a burning desire just to implement one single thing on top of it they will probably have an internal model that is very much focused on the problem they are solving.This practical need can induce them to create a new formal model or just sidestep lots of non-issues that would be a burden to deal with.
Yes, Feynman kept telling people to find problems that appeal to them and try to work them out, and not rely so heavily on reading. (One source: Feynman Lectures on Computation.)
In fact, the origins of OOP are basically what we would now call microservice architecture (CSP-inspired languages like Go being a specialisation of this). Each service can be as stateless or as stateful as it needs to be (without shared state) and services should be loosely coupled.
That's quite different to most large-scale OOP architectures, but it is possible to code in that style in any OOP language.
Heavy reliance on inheritance for code reuse is a whole problem class in itself that again has little to do with OOP and lots to do with the influence of C++.