Like you say, the main benefit of OOP is that it matches the way we perceive reality. That can make it intuitive to design software. However, this often comes at the expense of performance since the hardware that executes your system bears no resemblance to the way we perceive reality.
Take your physical simulations, for example. You may have a game scene with hundreds or thousands of entities. It's natural to try to model these as objects. In every frame you will then call a method on each object that applies a translation to its position. Very intuitive and simple. But unfortunately extremely inefficient for a computer to execute. You will likely pay a number of cache misses while jumping to each of your objects. And once you get there, the rest of the cache line where your coordinates are is full irrelevant stuff and therefore wasted (the other fields of your object).
For performance, you would want to have all your entities' coordinates in continuous memory one after another, and then apply a function to translate them all at once. Pretty much the opposite design from where OOP naturally leads you.
I kind of stopped understanding this explanation of inheritance (when I switched to composition of course ;) ). When I look at the world I see a universe of composition, we get molecules by composing atoms, atoms from composing protons, neutrons, etc. little "modules" that come together. More practically, everyday objects seem to follow this too. A motorcycle and car aren't both "refined" from some "wheeled vehicle" that car companies buy, rather they share some similar components that car companies buy. Their similarities are thus either emergent (interface/protocol) or a direct result of their shared components. In unity for example, you don't inherit every hit testable thing from some class (like you would in cocoa), but rather attach a collider to things that can be hit tested. The collider actually worries about one concern, and you can therefor conveniently attach multiple colliders to one object for example (makes it easy to form complex hit regions). Similarly things that are visible have renderers attached. This matches my intuition of working with real objects, where is buy parts and put them together, not think to myself which "master" part my new thing derives from, purchase it, then rip it open and start modifying it as step 1.
It's debatable whether OOP "matches the way we perceive reality". Many have come to believe this, but is it true? I don't perceive all of reality as a strict hierarchy of things constructed out of templates, with "is a" and "has a" relationships between them.
I don't think the main problem with OOP is performance, either. The problem is that it's not always the right or the most natural design approach, regardless of any performance considerations.
It's debatable whether OOP "matches the way we perceive reality". Many have come to believe this, but is it true?
No, it isn't true. But neither do we perceive all of reality as a collection entities that never change but are used to create new unchanging entities by a series of idempotent mappings from domains to codomains.
But both abstractions are useful as ways to organize your thoughts, or as languages we can use to describe processes in an organized manner. Which one is more useful is often case specific - with the case covering not only the problem space but also the individual psychology of the person doing the work.
Many find that neither is so useful that they would be comfortable using it to the exclusion of the other, which is why there are so many functional languages with object-oriented features such as Caml and why so many major object-oriented languages such as C++, C# and Java have been acquiring increasingly many functional features over the past decade.
I'm not sure why immutability is being lumped into OOP alternatives, seems orthogonal (to me). You can have immutable AND mutable versions of OOP after all. Similarly, you can have immutable and mutable composability. To me the main issue with OOP not matching reality is having people put things into hierarchies instead of thinking of common elements found in them. For example, to me thinking about composing objects in Go and its nice-auto-applying interfaces is similar to Haskell's data with typeclasses, despite one being totally mutable and the other immutable (obviously these feature sets are quite different, my point is in how you think about "the world", i.e. a circle in both Go and Haskell doesn't inherit from anything but may abide by a contract thanks to either interfaces or typeclasses, getting you to talk about things in terms of its features and not its inheritance).
struct Cat {
meow: String
}
impl Cat {
fn talk(self) {
println!(self.meow);
}
}
fn main() {
let bob = Cat { meow: "Mroowwwww!" };
bob.talk(); // Prints "Mroowwwww!" Good job, bob.
}
Cat lumps the data and its associated functionality together into an object. This is object-oriented programming even if you never bring inheritance into the picture.
Both Go and Rust have objects, they simply go by the name struct. Haskell does not follow the object-oriented approach.
I think the confusion surrounding OOP is that people associate OOP with a particular implementation of it, like Java.
Sure, but with that wide a definition its really hard for a programming language not to be object oriented. C can be just as object oriented with structs, unless you consider bob.talk() to be just completely conceptually different from talk(bob) where talk's definition expects a Cat struct. Similarly, under your example, Haskell is also object oriented since you have data types with fields and functions that can only operate on that kind of data (Again at this point you'd really be arguing that the order of function/caller is the "OOP differentiator" since (ignoring inheritance), there is no difference between Cat's talk method and a talk function that takes in a Cat).
In other words, I don't think anyone is ever arguing against organization of data and functions into more abstract "types". Pascal does this with Records, C with structs, Haskell with data types, C++ with classes, JavaScript with prototypes, etc etc. So if thats all it takes to be "OOP" (in other words, not forcing you to only use ints and floats), then I guess I agree that OOP is a better representation of the world. But now I'd argue that the more interesting discussion is between the has-a and is-a versions of this.
You have to draw the line somewhere. The structure of a typical OOP language program such Go, Rust, Java, C++, C#, etc. are not comparable to that of a typical Haskell program.
All of the languages that are considered OO rely heavily on the binding of data and functions into objects.
Others will have deeper ideas of what an OO language needs to have, but the basic definition is about objects. That is, the binding of data and functions into an object.
In modern OOP discussion, it seems many already have come to the conclusion that inheritance is something to be used very sparingly, and can be done away with in favor of composition in most use cases.
I agree that you have to draw the line somewhere, but I believe the is-a/has-a dichotomy more accurately separates the different modes of thinking. To me the structure of a Go program is completely indistinguishable from one in C++ (precisely because of the lack of inheritance). When I look at the design of a C++ program (or say Obj-C), its all about class hierarchies. The docs are all about the class diagrams, step 1 of most those programs is usually "subclass ___". It immediately drops you into that view of the world, and I believe that with that view comes the guiding hand of your program's design.
Compare that to Go, which focuses on the traits of objects instead of their incidental ancestry. In go you'd define a function or method applying to an abstract interface that has certain properties. For example, I would say "give me an object that has a show method", not "give me something inheriting from Printable". This is completely analogous to the very abstract typeclass-style programming you do in Haskell ("give me something that derives Show"). Haskell object architecture is all about defining an abstract typeclass and reasoning about what you can do given these existing methods. You then supply an implementation that fits the type class definition, exactly the same abstract analysis of fundamental properties divorced from their specific owners as inheritance-less interface/protocol programming in a language like Go.
Again, there is quite literally no difference in "binding" a function to an object through the dot syntax vs through type. If in haskell you say the meow function applies to the Cat data type its not any different than having a meow() method on a Cat in C++, neither can call that on anything else, its quite bound.
Also, I agree with you about the point of C being used in an OO fashion. I think this goes to show that a language doesn't need to specifically support objects/methods to wield in an OO-manner, it is just more inconvenient.
Haskell lacks basic OO features but that in itself is a feature. Methods are often procedural in nature. They're also not generalized since they are a concrete implementation for a specific type. Both of these are completely contrary to Haskell style.
Rust's trait system is essentially identical to Haskell's typeclasses, and Haskell allows infix operators which work exactly like method calls, except that you don't have to put a period between the data and the function. So it seems to me that by your definition Haskell is also object oriented.
(As for my personal opinion: I think the confusion around OOP is largely a product of it not having a precise definition).
Agreed. I kind of miss the good old days when the dichotomy was functional/imperative (which is much more valid), and the prevalent rhetoric didn't seem quite so eager to erase the likes of Caml and Dylan from the pages of history.
Many have come to believe this, but is it true? I don't perceive all of reality as a strict hierarchy of things constructed out of templates, with "is a" and "has a" relationships between them.
The strict hierarchy thing is more a feature of the class-oriented programming of C++ and its close relatives (an artifact of what made sense when approximating OOP in something merged into the existing syntax and type system of C) than it is inherent to OOP.
Unfortunately, the popularity of C++ and Java means that its hard to tell when people are talking about "Object Oriented" if they mean it in the general sense that existed prior to those languages which they are one of many approaches to facilitating, or if it means it in the narrow, static-class-oriented sense which evolved out of the particular approaches of those languages and their close relatives, which is something different.
At a high level a lot of systems are made of some kind of objects or actors. Things that transmit and receive signals or messages. Telecoms, the internet, Unix processes, micro services, smoke signals, human conversation and instruction. So maybe you can do pure functional down below but at a high level your system will always have some kind of objects (for want of a better word). Plus at a high level these systems don't share state. They can only do so via signals. This turns out to be a good enough solution for concurrency. People can demonstrate how you can still get deadlocks etc but if the granularity is large enough this rarely turns out to be a problem in practice.
Is-a and has-a, and more to the point, naming things, is just an outgrowth of our natural linguistic capabilities. The alternative is anonymous immutable values with structural comparisons (aka functional programming), which we've only been studing as math for a couple of thousand years or so (vs. Around 100k years for language).
I don't know if the alternative is functional programming or structural comparisons, and I wasn't arguing that in this particular case. I agree about naming things, but I don't consider it an OO exclusive.
What I'm saying is that there are a couple of assertions, more or less accepted in the industry, that may be flawed or simply false, and that may have led to the current mess of software design:
1- That OO decomposition and design reflects "how we perceive the world".
2- That if there is merit to this notion that we naturally decompose/understand the world in terms of objects, has-a and is-a, that this maps to the set of formalisms we usually call OO Design & Programming. For example, it's obvious to me "message passing" is a (sometimes useful) abstraction completely alien to how we perceive the world -- nobody thinks in terms of sending messages to the objects. A message is something you send to another sentient being, preferably a person.
I agree about naming things, but I don't consider it an OO exclusive.
It is not an OO exclusive but it is at the foundation of OO (naming things, relating them, and such). Math (outside of maybe graph theory) is the exact opposite (names lie about fundamental truth, let's work with truth); that is the direction that pure functional programming really takes (many reduce it to immutability, but that is just a consequence of not having identity and aliases).
1- That OO decomposition and design reflects "how we perceive the world".
That is not really how its pushed. It is more about how we "talk" about the world. After all, humans are writing the programs, probably together, talking to stake holders and all. OO is not the best way to talk to the computer, but it might be the best way to talk to humans about programs until we can evolve into more Vulcan like creatures.
Also, are evolved capability for language has greatly influenced how we think and solve problems. People find it easy to talk about things, ascribe names to them, and arbitrarily relate them to other things. This really gets in the way of learning math for most people (names and ascribed relations are informal, they can lie), but it is very accessible to deal with the computer world like you would the physical world (even if it is often technically wrong).
For example, it's obvious to me "message passing" is a (sometimes useful) abstraction completely alien to how we perceive the world -- nobody thinks in terms of sending messages to the objects. A message is something you send to another sentient being, preferably a person.
Many people anthropomorphize their objects, which again is naturally human; again, objects are not for computers, they are for people.
[Naming things] is not an OO exclusive but it is at the foundation of OO (naming things, relating them, and such).
Naming things is at the foundation of most human activities, making it less than useful to define OO.
[That OO decomposition and design reflects "how we perceive the world"] is not really how its pushed.
Sorry, but I disagree. It is. In this very HN thread we're replying to, for example.
Many people anthropomorphize their objects, which again is naturally human; again, objects are not for computers, they are for people.
Now you are trying too hard. No, it's unnatural to speak of "passing messages to objects". This isn't how people understand the real world (except for old ladies who speak to their plants, but they are not the target audience of Smalltalk or Objective-C), and in fact message passing is one of the most alien (and harder to understand) aspects of the OOP abstraction. So alien, in fact, that some OO languages do away with this terminology.
Naming things is at the foundation of most human activities, making it less than useful to define OO.
Yep, thinking in terms of "objects" is kind of broad and fundamental. It is preferable to the "OO is Java" definition that is often pushed.
Sorry, but I disagree. It is. In this very HN thread we're replying to, for example.
I was presenting what I thought was what really is seen as the benefits of OOP.
Now you are trying too hard. No, it's unnatural to speak of "passing messages to objects". This isn't how people understand the real world (except for old ladies who speak to their plants, but they are not the target audience of Smalltalk or Objective-C), and in fact message passing is one of the most alien (and harder to understand) aspects of the OOP abstraction. So alien, in fact, that some OO languages do away with this terminology.
Anthropomorphisms are as old as Aesop; heck before we had much formal science, it was are only way of understanding things (reading Plato and Aristotle). Message passing is just communicating with something, you might not ever think "we have to tell that object to update itself" but plenty of people do. Note I'm not really a big fan of message passing, and it is hardly something exclusive to OO (my colleagues are very much into RPC without an object in sight).
Anthropomorphisms are as old as Aesop; heck before we had much formal science, it was are only way of understanding things (reading Plato and Aristotle). Message passing is just communicating with something, you might not ever think "we have to tell that object to update itself" but plenty of people do.
I understand what you're saying, I just disagree with it. No-one I know thinks in terms of "I have to send the teapot the message to pour tea"; it's just an unnatural way of thinking. Our old lady from the example may think of her lovely Chinese teapot as a "she", might even name it, but she still won't think in terms of sending messages to pour tea. People don't think that way. And that's alright: OOP is a formalism (like Math, only probably less formal), not a "natural" description of the world.
Note I'm not really a big fan of message passing, and it is hardly something exclusive to OO (my colleagues are very much into RPC without an object in sight).
Message passing was defined by the inventor of OOP as its defining feature. Of course, Java, C++ et. al. then subverted this, but that's an entirely different debate.
----
To make this debate more constructive: I think OOP is valuable as a way to do modularization. Modularization is a worthy goal, but OOP is just one way to do it. Not the best way, but the one most programmers are familiar with, regrettably to the exclusion of other approaches.
I disagree with Kay on his assessment of OO, I disagree that he's even the inventor of it (though he coined the term, word out to the Scandinavians and even Sutherland).
I would claim that OOP is not about modularization at all; it is a way of thinking meant to help humans solve problems with a computer. It's a crutch, it is easy to apply, and has lots limitations. FP (thinking in terms of anonymous values vs. named objects) is an alternative, though more experienced programmers often use both ways of thinking where best appropriate (which is why you'll see OOP entity interfaces in languages like Clojure, and lots of immutable structs in C#). Neither can claim a decisive benefit in modularity or code reuse; you have to work extra hard for those.
Thank you for raising this. All too often debates about OOP get hung up on the overuse of inheritance. That’s a fair point, to be sure, but IMHO there are two much more fundamental limitations that are inherent in the OOP way of doing things.
1. In OOP, one object is special.
Whether it’s the object that is receiving a message or the object that gets a special designation like `this` or `self`, something is always given a special emphasis in a purely OOP design.
However, many useful algorithms take multiple inputs where none needs to be singled out in that way. Where do such algorithms live?
Trivial example: A symmetric binary operator like +.
Grown-up example: You don’t really model funds transfers by sending a `debit` message to one instance of a `BankAccount` class and a `credit` message to another instance.
2. In OOP, one object is special.
This is a generalisation of the point that zmb_ made. Usually in programming we don’t work only with single, self-contained data points. Most interesting things happen when we manipulate structured data and consider relationships between data points.
In purely OOP designs, we often see classes representing single data points, and then further classes to represent containers of that type. However, if the implementation of each data point is locked up behind the interface to a particular class, and then we have to access the points in each container through the container’s own generic interface, we are constrained in the access patterns we can use. Emphasizing individual, self-contained data points is often the wrong level of granularity for promoting either code reuse or efficient designs.
Example 1: What if we want to implement a more efficient representation of a data set that supports a different access pattern, such as the kind of continuous memory case zmb_ mentioned?
Example 2: What if we want to enforce constraints on a whole set of data, or model relationships between structured data of different types?
In each case, it may be very difficult to reuse existing algorithms that are locked up in methods on existing single-data-point classes. We might want to store the underlying data in a different format, and converting between formats just to access functionality artificially tied to a specific variation is likely to be awkward and inefficient.
If we instead build our modules as a set of fundamental data types and a set of accompanying algorithms — which could be as simple as a library of C structs/enums and functions using them — then the artificial barrier doesn’t arise. We can still present a clean interface/implementation for each module as a whole, but we aren’t forcing the implementation details to be separated just because Everything Must Be A Class.
In #1, your "trivial" example is actually more of a real issue with OOP than your "grown up" one, since the the latter just illustrates poorly chosen objects and messages, not an issue with OOP as such.
In #2, you again just illustrate a potential poor choice of objects. In both examples for this point, the answer to the "What if" is "you create an object that modes the data set, rather than the individual data points". This is not only consistent with OOP, its fairly routine.
In both examples for this point, the answer to the "What if" is "you create an object that modes the data set, rather than the individual data points". This is not only consistent with OOP, its fairly routine.
And how are you going to implement those “data set” objects, if the only tool you have in your armoury is more objects? Where are you going to put code that works with the underlying data and is useful regardless of the specific structure that data happens to be kept in at the time?
You can model functions as objects and run all sorts of function.call(args) methods, but if it is done out of necessity, it doesn't matter if it is done regularly or consistently with OOP -- OOP is just not the best way to go with this sort of thing.
OOP is not that slow in a typical application. Pure functional programming's emphasis on copying everything causing excessive garbage has an impact on performance, too.
OOP's problem with performance is that it doesn't play nice with modern CPU's L1/L2 cache since most typical OOP implementations don't allocate objects in contiguous memory. But only a very narrow set of problem niches such as high data volume high performance simulation require packing data tightly to take advantage of L1/L2 cache. The hundreds or thousands of entities using OOP in a game won't cause a sweat. The cache problem would have an impact when there are hundreds of thousands or millions of entities and you need to process them 60 times per second.
The L1/L2 cache performance hit can exist in functional program, too. The list and it cells in LISP are not allocated contiguously. Basically any non-array data structures would not play nice with cache.
That just means to use the right data structures for the right job. For high data volume and high performance processing, use array. Whether it's used in the context of OOP is irrelevant.
Ugh, pure functional programming language implementations don't generally actually copy everything. Because data structures are often immutable, pointers can be used rather than copying.
You're assuming that an object is always going to be modelled as a heap-allocated struct-like block of memory containing all of its state. There's no reason that has to be the case, though. You could create a system with an object-oriented programming model whose data storage was array-backed/table-oriented. You could probably even code something like that up in a fully GC'd OO language like C#. The way Java and C# manage string intern tables is an example of the kind of specialized data backing that's possible behind externally OO interfaces.
Comments
Like you say, the main benefit of OOP is that it matches the way we perceive reality. That can make it intuitive to design software. However, this often comes at the expense of performance since the hardware that executes your system bears no resemblance to the way we perceive reality.
Take your physical simulations, for example. You may have a game scene with hundreds or thousands of entities. It's natural to try to model these as objects. In every frame you will then call a method on each object that applies a translation to its position. Very intuitive and simple. But unfortunately extremely inefficient for a computer to execute. You will likely pay a number of cache misses while jumping to each of your objects. And once you get there, the rest of the cache line where your coordinates are is full irrelevant stuff and therefore wasted (the other fields of your object).
For performance, you would want to have all your entities' coordinates in continuous memory one after another, and then apply a function to translate them all at once. Pretty much the opposite design from where OOP naturally leads you.
I kind of stopped understanding this explanation of inheritance (when I switched to composition of course ;) ). When I look at the world I see a universe of composition, we get molecules by composing atoms, atoms from composing protons, neutrons, etc. little "modules" that come together. More practically, everyday objects seem to follow this too. A motorcycle and car aren't both "refined" from some "wheeled vehicle" that car companies buy, rather they share some similar components that car companies buy. Their similarities are thus either emergent (interface/protocol) or a direct result of their shared components. In unity for example, you don't inherit every hit testable thing from some class (like you would in cocoa), but rather attach a collider to things that can be hit tested. The collider actually worries about one concern, and you can therefor conveniently attach multiple colliders to one object for example (makes it easy to form complex hit regions). Similarly things that are visible have renderers attached. This matches my intuition of working with real objects, where is buy parts and put them together, not think to myself which "master" part my new thing derives from, purchase it, then rip it open and start modifying it as step 1.
It's debatable whether OOP "matches the way we perceive reality". Many have come to believe this, but is it true? I don't perceive all of reality as a strict hierarchy of things constructed out of templates, with "is a" and "has a" relationships between them.
I don't think the main problem with OOP is performance, either. The problem is that it's not always the right or the most natural design approach, regardless of any performance considerations.
No, it isn't true. But neither do we perceive all of reality as a collection entities that never change but are used to create new unchanging entities by a series of idempotent mappings from domains to codomains.
But both abstractions are useful as ways to organize your thoughts, or as languages we can use to describe processes in an organized manner. Which one is more useful is often case specific - with the case covering not only the problem space but also the individual psychology of the person doing the work.
Many find that neither is so useful that they would be comfortable using it to the exclusion of the other, which is why there are so many functional languages with object-oriented features such as Caml and why so many major object-oriented languages such as C++, C# and Java have been acquiring increasingly many functional features over the past decade.
I'm not sure why immutability is being lumped into OOP alternatives, seems orthogonal (to me). You can have immutable AND mutable versions of OOP after all. Similarly, you can have immutable and mutable composability. To me the main issue with OOP not matching reality is having people put things into hierarchies instead of thinking of common elements found in them. For example, to me thinking about composing objects in Go and its nice-auto-applying interfaces is similar to Haskell's data with typeclasses, despite one being totally mutable and the other immutable (obviously these feature sets are quite different, my point is in how you think about "the world", i.e. a circle in both Go and Haskell doesn't inherit from anything but may abide by a contract thanks to either interfaces or typeclasses, getting you to talk about things in terms of its features and not its inheritance).
Inheritance is not necessary for OOP.
For example, if you have:
Cat lumps the data and its associated functionality together into an object. This is object-oriented programming even if you never bring inheritance into the picture.Both Go and Rust have objects, they simply go by the name struct. Haskell does not follow the object-oriented approach.
I think the confusion surrounding OOP is that people associate OOP with a particular implementation of it, like Java.
Sure, but with that wide a definition its really hard for a programming language not to be object oriented. C can be just as object oriented with structs, unless you consider bob.talk() to be just completely conceptually different from talk(bob) where talk's definition expects a Cat struct. Similarly, under your example, Haskell is also object oriented since you have data types with fields and functions that can only operate on that kind of data (Again at this point you'd really be arguing that the order of function/caller is the "OOP differentiator" since (ignoring inheritance), there is no difference between Cat's talk method and a talk function that takes in a Cat).
In other words, I don't think anyone is ever arguing against organization of data and functions into more abstract "types". Pascal does this with Records, C with structs, Haskell with data types, C++ with classes, JavaScript with prototypes, etc etc. So if thats all it takes to be "OOP" (in other words, not forcing you to only use ints and floats), then I guess I agree that OOP is a better representation of the world. But now I'd argue that the more interesting discussion is between the has-a and is-a versions of this.
You have to draw the line somewhere. The structure of a typical OOP language program such Go, Rust, Java, C++, C#, etc. are not comparable to that of a typical Haskell program.
All of the languages that are considered OO rely heavily on the binding of data and functions into objects.
Others will have deeper ideas of what an OO language needs to have, but the basic definition is about objects. That is, the binding of data and functions into an object.
In modern OOP discussion, it seems many already have come to the conclusion that inheritance is something to be used very sparingly, and can be done away with in favor of composition in most use cases.
I agree that you have to draw the line somewhere, but I believe the is-a/has-a dichotomy more accurately separates the different modes of thinking. To me the structure of a Go program is completely indistinguishable from one in C++ (precisely because of the lack of inheritance). When I look at the design of a C++ program (or say Obj-C), its all about class hierarchies. The docs are all about the class diagrams, step 1 of most those programs is usually "subclass ___". It immediately drops you into that view of the world, and I believe that with that view comes the guiding hand of your program's design.
Compare that to Go, which focuses on the traits of objects instead of their incidental ancestry. In go you'd define a function or method applying to an abstract interface that has certain properties. For example, I would say "give me an object that has a show method", not "give me something inheriting from Printable". This is completely analogous to the very abstract typeclass-style programming you do in Haskell ("give me something that derives Show"). Haskell object architecture is all about defining an abstract typeclass and reasoning about what you can do given these existing methods. You then supply an implementation that fits the type class definition, exactly the same abstract analysis of fundamental properties divorced from their specific owners as inheritance-less interface/protocol programming in a language like Go.
Again, there is quite literally no difference in "binding" a function to an object through the dot syntax vs through type. If in haskell you say the meow function applies to the Cat data type its not any different than having a meow() method on a Cat in C++, neither can call that on anything else, its quite bound.
Also, I agree with you about the point of C being used in an OO fashion. I think this goes to show that a language doesn't need to specifically support objects/methods to wield in an OO-manner, it is just more inconvenient.
Haskell lacks basic OO features but that in itself is a feature. Methods are often procedural in nature. They're also not generalized since they are a concrete implementation for a specific type. Both of these are completely contrary to Haskell style.
Rust's trait system is essentially identical to Haskell's typeclasses, and Haskell allows infix operators which work exactly like method calls, except that you don't have to put a period between the data and the function. So it seems to me that by your definition Haskell is also object oriented.
(As for my personal opinion: I think the confusion around OOP is largely a product of it not having a precise definition).
Agreed. I kind of miss the good old days when the dichotomy was functional/imperative (which is much more valid), and the prevalent rhetoric didn't seem quite so eager to erase the likes of Caml and Dylan from the pages of history.
The strict hierarchy thing is more a feature of the class-oriented programming of C++ and its close relatives (an artifact of what made sense when approximating OOP in something merged into the existing syntax and type system of C) than it is inherent to OOP.
Unfortunately, the popularity of C++ and Java means that its hard to tell when people are talking about "Object Oriented" if they mean it in the general sense that existed prior to those languages which they are one of many approaches to facilitating, or if it means it in the narrow, static-class-oriented sense which evolved out of the particular approaches of those languages and their close relatives, which is something different.
At a high level a lot of systems are made of some kind of objects or actors. Things that transmit and receive signals or messages. Telecoms, the internet, Unix processes, micro services, smoke signals, human conversation and instruction. So maybe you can do pure functional down below but at a high level your system will always have some kind of objects (for want of a better word). Plus at a high level these systems don't share state. They can only do so via signals. This turns out to be a good enough solution for concurrency. People can demonstrate how you can still get deadlocks etc but if the granularity is large enough this rarely turns out to be a problem in practice.
Is-a and has-a, and more to the point, naming things, is just an outgrowth of our natural linguistic capabilities. The alternative is anonymous immutable values with structural comparisons (aka functional programming), which we've only been studing as math for a couple of thousand years or so (vs. Around 100k years for language).
I don't know if the alternative is functional programming or structural comparisons, and I wasn't arguing that in this particular case. I agree about naming things, but I don't consider it an OO exclusive.
What I'm saying is that there are a couple of assertions, more or less accepted in the industry, that may be flawed or simply false, and that may have led to the current mess of software design:
1- That OO decomposition and design reflects "how we perceive the world".
2- That if there is merit to this notion that we naturally decompose/understand the world in terms of objects, has-a and is-a, that this maps to the set of formalisms we usually call OO Design & Programming. For example, it's obvious to me "message passing" is a (sometimes useful) abstraction completely alien to how we perceive the world -- nobody thinks in terms of sending messages to the objects. A message is something you send to another sentient being, preferably a person.
It is not an OO exclusive but it is at the foundation of OO (naming things, relating them, and such). Math (outside of maybe graph theory) is the exact opposite (names lie about fundamental truth, let's work with truth); that is the direction that pure functional programming really takes (many reduce it to immutability, but that is just a consequence of not having identity and aliases).
That is not really how its pushed. It is more about how we "talk" about the world. After all, humans are writing the programs, probably together, talking to stake holders and all. OO is not the best way to talk to the computer, but it might be the best way to talk to humans about programs until we can evolve into more Vulcan like creatures.
Also, are evolved capability for language has greatly influenced how we think and solve problems. People find it easy to talk about things, ascribe names to them, and arbitrarily relate them to other things. This really gets in the way of learning math for most people (names and ascribed relations are informal, they can lie), but it is very accessible to deal with the computer world like you would the physical world (even if it is often technically wrong).
Many people anthropomorphize their objects, which again is naturally human; again, objects are not for computers, they are for people.
Naming things is at the foundation of most human activities, making it less than useful to define OO.
Sorry, but I disagree. It is. In this very HN thread we're replying to, for example.
Now you are trying too hard. No, it's unnatural to speak of "passing messages to objects". This isn't how people understand the real world (except for old ladies who speak to their plants, but they are not the target audience of Smalltalk or Objective-C), and in fact message passing is one of the most alien (and harder to understand) aspects of the OOP abstraction. So alien, in fact, that some OO languages do away with this terminology.
Yep, thinking in terms of "objects" is kind of broad and fundamental. It is preferable to the "OO is Java" definition that is often pushed.
I was presenting what I thought was what really is seen as the benefits of OOP.
Anthropomorphisms are as old as Aesop; heck before we had much formal science, it was are only way of understanding things (reading Plato and Aristotle). Message passing is just communicating with something, you might not ever think "we have to tell that object to update itself" but plenty of people do. Note I'm not really a big fan of message passing, and it is hardly something exclusive to OO (my colleagues are very much into RPC without an object in sight).
I understand what you're saying, I just disagree with it. No-one I know thinks in terms of "I have to send the teapot the message to pour tea"; it's just an unnatural way of thinking. Our old lady from the example may think of her lovely Chinese teapot as a "she", might even name it, but she still won't think in terms of sending messages to pour tea. People don't think that way. And that's alright: OOP is a formalism (like Math, only probably less formal), not a "natural" description of the world.
Message passing was defined by the inventor of OOP as its defining feature. Of course, Java, C++ et. al. then subverted this, but that's an entirely different debate.
----
To make this debate more constructive: I think OOP is valuable as a way to do modularization. Modularization is a worthy goal, but OOP is just one way to do it. Not the best way, but the one most programmers are familiar with, regrettably to the exclusion of other approaches.
I disagree with Kay on his assessment of OO, I disagree that he's even the inventor of it (though he coined the term, word out to the Scandinavians and even Sutherland).
I would claim that OOP is not about modularization at all; it is a way of thinking meant to help humans solve problems with a computer. It's a crutch, it is easy to apply, and has lots limitations. FP (thinking in terms of anonymous values vs. named objects) is an alternative, though more experienced programmers often use both ways of thinking where best appropriate (which is why you'll see OOP entity interfaces in languages like Clojure, and lots of immutable structs in C#). Neither can claim a decisive benefit in modularity or code reuse; you have to work extra hard for those.
We can definitely agree to disagree on this.
Thank you for raising this. All too often debates about OOP get hung up on the overuse of inheritance. That’s a fair point, to be sure, but IMHO there are two much more fundamental limitations that are inherent in the OOP way of doing things.
1. In OOP, one object is special.
Whether it’s the object that is receiving a message or the object that gets a special designation like `this` or `self`, something is always given a special emphasis in a purely OOP design.
However, many useful algorithms take multiple inputs where none needs to be singled out in that way. Where do such algorithms live?
Trivial example: A symmetric binary operator like +.
Grown-up example: You don’t really model funds transfers by sending a `debit` message to one instance of a `BankAccount` class and a `credit` message to another instance.
2. In OOP, one object is special.
This is a generalisation of the point that zmb_ made. Usually in programming we don’t work only with single, self-contained data points. Most interesting things happen when we manipulate structured data and consider relationships between data points.
In purely OOP designs, we often see classes representing single data points, and then further classes to represent containers of that type. However, if the implementation of each data point is locked up behind the interface to a particular class, and then we have to access the points in each container through the container’s own generic interface, we are constrained in the access patterns we can use. Emphasizing individual, self-contained data points is often the wrong level of granularity for promoting either code reuse or efficient designs.
Example 1: What if we want to implement a more efficient representation of a data set that supports a different access pattern, such as the kind of continuous memory case zmb_ mentioned?
Example 2: What if we want to enforce constraints on a whole set of data, or model relationships between structured data of different types?
In each case, it may be very difficult to reuse existing algorithms that are locked up in methods on existing single-data-point classes. We might want to store the underlying data in a different format, and converting between formats just to access functionality artificially tied to a specific variation is likely to be awkward and inefficient.
If we instead build our modules as a set of fundamental data types and a set of accompanying algorithms — which could be as simple as a library of C structs/enums and functions using them — then the artificial barrier doesn’t arise. We can still present a clean interface/implementation for each module as a whole, but we aren’t forcing the implementation details to be separated just because Everything Must Be A Class.
In #1, your "trivial" example is actually more of a real issue with OOP than your "grown up" one, since the the latter just illustrates poorly chosen objects and messages, not an issue with OOP as such.
In #2, you again just illustrate a potential poor choice of objects. In both examples for this point, the answer to the "What if" is "you create an object that modes the data set, rather than the individual data points". This is not only consistent with OOP, its fairly routine.
In both examples for this point, the answer to the "What if" is "you create an object that modes the data set, rather than the individual data points". This is not only consistent with OOP, its fairly routine.
And how are you going to implement those “data set” objects, if the only tool you have in your armoury is more objects? Where are you going to put code that works with the underlying data and is useful regardless of the specific structure that data happens to be kept in at the time?
You can model functions as objects and run all sorts of function.call(args) methods, but if it is done out of necessity, it doesn't matter if it is done regularly or consistently with OOP -- OOP is just not the best way to go with this sort of thing.
OOP is not that slow in a typical application. Pure functional programming's emphasis on copying everything causing excessive garbage has an impact on performance, too.
OOP's problem with performance is that it doesn't play nice with modern CPU's L1/L2 cache since most typical OOP implementations don't allocate objects in contiguous memory. But only a very narrow set of problem niches such as high data volume high performance simulation require packing data tightly to take advantage of L1/L2 cache. The hundreds or thousands of entities using OOP in a game won't cause a sweat. The cache problem would have an impact when there are hundreds of thousands or millions of entities and you need to process them 60 times per second.
The L1/L2 cache performance hit can exist in functional program, too. The list and it cells in LISP are not allocated contiguously. Basically any non-array data structures would not play nice with cache.
That just means to use the right data structures for the right job. For high data volume and high performance processing, use array. Whether it's used in the context of OOP is irrelevant.
Ugh, pure functional programming language implementations don't generally actually copy everything. Because data structures are often immutable, pointers can be used rather than copying.
Just a minor nit.
You're assuming that an object is always going to be modelled as a heap-allocated struct-like block of memory containing all of its state. There's no reason that has to be the case, though. You could create a system with an object-oriented programming model whose data storage was array-backed/table-oriented. You could probably even code something like that up in a fully GC'd OO language like C#. The way Java and C# manage string intern tables is an example of the kind of specialized data backing that's possible behind externally OO interfaces.