My definition of an object is "an ADT that gets to 'decide', using private runtime state, what to do in response to any/all attempts to interact with it."
Which makes for a very simple test: if something is an object, you should be able to send it a message that makes it change its mind about what messages it accepts after that.
There's no obvious way to implement a behavior like this using the native "objects" in nominally-object-oriented languages like C++ or Java. (But you can do so with the closure types from these languages. Or you can look higher on the stack: threads with IPC queues, or POSIX processes, are both objects.)
But implementing this behavior is obvious in Ruby; this is how basic things like Object#extend work.
In conclusion, Erlang is more object-oriented than Java. ;)
---
And yeah, I know, this definition of OOP-ness sort of flies in the face of decades of things people have said about OOP. Objects, under this definition of OOP, don't obey any of the SOLID principles. They're slippery; unpredictable; "alive."
But this was the original definition! It's the one Smalltalk fits; it's the one LambdaMOO fits; it's what is meant by referring to Javascript as object-oriented.
It's a shame we have this collision between meanings; having two separate words for "OOP like Smalltalk" and "OOP like Java" would have saved language-designers all a world of arguments from people who try to propose extensions to a language in one category, assuming it's in the other.
Your definition of an object is similar to one that I’ve used[1][2][3], which is unsurprising since we got it from the same place.
But, now I define an object as any component with which you interact strictly by way of negotiation (as opposed to command and control), and I finally think I understand why Alan Kay says that the most important thing in OOP is the message passing.
Thus, statically-typed languages like C#/Java/C++/etc. are also capable of being object-oriented as long as you avoid setters and realize that some of the negotiation happens at compile-time.
I'm not sure what's the problem with setters? In my eyes, they are just a convenient shorthand for a common message. For example, Objective-C before version 2.0 didn't have setters; so the libraries had lots of methods like "setTitle:", "setDelegate:" etc. Setters are just a convenient shorthand. And in ObjC and in Swift (I don't know Java/C#/C++ so well) you can override setters to do whatever you like, so there really isn't much of a difference between using a setter and sending a message -- it's just syntactic sugar that makes code more readable.
ironically the "java/c#/c++"-ness in this case is precisely a point of difference with objective-c, which has messages, while java has only method invokations. meaning a setter in c++ is (more or less) syntactic sugar for setFoo(objectref, newval), while in objective-c it's more like sendMessage("setFoo",objectref, newval).
the difference might seem minor but the extra layer of inderection means the target object can have centralised logic to handle the outcome of this message if it wamts to, as opposed to repeating and distributing such logic among every method, making it a more independent unit as opposed to something being directly manipulated from the outside.
put another way, the object can decide independently what to do with the message "setFoo" in objective C (or ruby), but in c++ there is no message, no decision, just some code that directly causes something to happen, that can be invoked directly from any other part of the program ar any time, whose results are then relied upon to be visible directly after invokation.
as a thought experiment, imagine if the target object actually exists on a different server, what would the different unsugared implementations need to look like? how would you pause the in progress program to wait for the result of the method invokation? what impact might that delay have on the user?
An object that derives from DynamicObject in C# can have conditionals in its TryInvoke override, so it can change which method calls it responds to. However this sacrifices useful static compile-time checks, so it only gets used in unusual situations where the extra dynamic behaviour outweighs the cost.
> whose results are then relied upon to be visible directly after invokation
I'm not sure I buy that, surely those details are down to the individual methods in question. There's lots of hairy (distributed) COM code out there that doesn't make that promise for example, and it won't hold in a multi-threaded environment for objects that are not explicitly thread-safe in any case.
By “setter”, I mean a mechanism by which code that is external to an object can gain unmediated access to one or more of the object’s mutable instance variables.
The big idea is that the sender should not be able to assume that the message, `setFoo`, will have any particular effect with respect to the reciever’s state.
At least in C# terminology, a "setter" for a public property is mediated access, unmediated access would be a public field that can be set without invoking a setter method.
Getters and setters in C# are method calls, like `setFoo` in Java, so can't be assumed to be simple, they can do anything any other method can do.
As it turns out, C# is a language that I know extremely well; I'm aware that setters can have arbitrary code. But, here's the problem: A programming a language is medium for thought, which means that it promotes or prevents the occurrence of certain ideas.
So, even though the compiler will accept any legal void-returning code within the inner braces of
public Foo Bar
{
set
{
//Do stuff
}
}
The fact that the client code is going to look like `Obj.Foo = whatever` will practically foreclose on all but a small subset of possibilities.
I think the problem is that, yeah, they're messages but very low level messages about the state of the object and not at all about a behaviour of the object in response to a message.
I think, however, they're a good way of building objects where a language lacks object or hashmap shorthand (e.g JS and Ruby). In fact, with Obj-C, if my memory serves right, it can look just as expressive.
Setters (particularly fluent), in that case, seem best for building objects in a readable way where a language lacks a way to pass express keyword arguments.
Thanks for letting me know - can't believe I missed that. I don't use Ruby professionally, just as a scripting language to make my life at work easier.
That's true, and often a well-designed object needs setters, but I think what the parent is getting at is using objects to model dumb data instead of behavior encapsulating state. If an object has getters and setters for all its members, then it isn't really an object. It's a degenerate object that is equivalent to naked data. Not much changes if it has only getters: then it's just immutable naked data.
(Which I think is a good thing, and I hate it when people make blanket assumptions otherwise! And I hate it when languages like Java make it awkward to express dumb immutable data. I think the pieces in a program that are best modeled as "behavior" and "state" is a subset, usually a proper one, often a small one, of the data and functionality that needs to be modeled in a nontrivial program. Often there's a lot of data in a program that is most clearly modeled as dumb data, or at least as transparent data packaged together with some common operations. "Behavior" and "state" is mental overhead that only pays for itself when you have to express a certain amount of complexity. That's why it's so important for a language to let you start in one style, with plain transparent data (and lots of immutability), and switch to encapsulated behavior when you discover that part of the code is complex enough to benefit from it. Otherwise you pay a high mental cost to future-proofing every piece of data with getters and setters and private data when it's possible that only a small portion of your code will ever benefit from it.)
> If an object has getters and setters for all its members, then it isn't really an object.
Minor quibble. Take for example a coordinate class, that you can get/set cartesian, and get/set polar. It's a nice example of an object with no private state, but transparently hides the coordinate system transformations.
In general, i agree with you. The vast majority are just (possibly immutable) structs.
But, now I define an object as any component with which you interact strictly by way of negotiation (as opposed to command and control), and I finally think I understand why Alan Kay says that the most important thing in OOP is the message passing.
That's actually a really good, concise definition. I might use this in future bikesheddings.
For me a setter is something that refers explicitly to data inside the object. It means the world outside the object is now intimately and inappropriately concerned with what's inside the object. The whole point of objects is to make them block boxes and ask them to do higher level things, and hide how they do those things to avoid information overload.
Even if a method does nothing but set an internal field of an object, in my eyes it's not a setter if it performs an actual high level task and doesn't expose the internal structure. IE:
monster.setDead(true);
Is a setter. While
monster.Kill();
Is not to me, even if it does the exact same thing inside. The latter doesn't bother me about internal details like some boolean field called "dead".
It's a subtle and almost pedantic distinction, but I hope you see where I'm coming from, and can appreciate the mindshift change from "this is a box full of fields" to "this is an entity".
I'd call that "still a setter". I think the concern is that if bar is more about the representation of the object than the behavior, you're not operating at the right level of abstraction.
And the other alternative, because an object still usually needs some type of values would be:
class Foo
{
Foo(string bar) { _bar = bar; } //Foo is the constructor
}
The benefit being that once an object is created, it is guaranteed to be in a valid state.
But by classical OO an object represents state + behavior. So why not do both?
Assign the value in the constructor to guarantee that the object is always in a valid state and use a property (if the object is an entity as opposed to a value object), when it needs to change?
The thing is, in simple cases the implementation will trivially parallel the sensible interface. So setters are appropriate.
The problem becomes using setters where that's not the case, often to support tooling that is dependent on constructing objects from serialized state (which often also involves the same kind of abuse of getters, to which exactly the same concerns apply.)
This is one of the few things about Ruby idioms that drive me a bit nuts--improper use of `attr_accessor` when `attr_reader` creates better and cleaner code is one of the leading code smells in the library ecosystem.
It's not about heavy-handedness, it's that `attr_accessor` breaks encapsulation of private state and code very rarely takes into account what actually updating that member variable does.
It's used mostly because novice Rubyists are told that that's how you make a variable accessible, not that that's how you make it read-writable. In C#, on the other hand, when you create a property that's the moral equivalent, there's a lot more of a focus on creating getter-only properties that reveal state but don't let you munge it.
Ruby is also pretty bad about using #freeze and #taint, but the latter is a lost cause.
> My definition of an object is "an ADT that gets to 'decide', using private runtime state, what to do in response to any/all attempts to interact with it."
Which makes for a very simple test: if something is an object, you should be able to send it a message that makes it change its mind about what messages it accepts after that.
I'm not sure these are equivalent criterion.
In Java and C++, the "dispatch matrix" is defined at compile time, and it is stored at runtime, and the object itself does determine the contents of that matrix. So these languages do clearly meet your first criterion. But also, clearly, not the second.
I guess the objection you might raise is "any/all messages", with "modify your dispatch matrix" being a reasonable message. But there will always be some message that cannot be implemented by the object; we can certainly code up some Goedelian thing inside of a core OO calculus. But for any concrete OO language, something more reasonable than that probable exists.
So then the question is where -- not if -- we draw the line on "any/all messages". And I'm not sure that drawing that line south of Ruby but north of Java makes a lot of sense.
Your definition makes a lot of sense to me. In pure programming languages, an object can appear when you run the code, but never before that. The pure description of the program cannot contain objects -- since objects carry state -- but when this description/code is executed/evaluated by the runtime system, a stateful object appears that you can interact with (e.g. a web server).
So, for pure programming languages, this "private state" is contained within the runtime system (out of reach for the programmer), rather than in the code itself, and the RTS applies pure functions to the state it carries, in order to reach a new program state.
To be entirely accurate, you're wrong when you say your definition of OOP is the original one. SIMULA-67 predates Smalltalk and its object system is much closer to Java than it is to Smalltalk, although it helped inspire both.
To be more fair, there are two very different schools which interpreted and continued to developed Simula in two radically divergent ways.
One of the schools is focusing on clear division between rigid, statically defined classes that never change (but may extend other classes) and runtime instances, which can contain only state, not behavior. This school does everything exhaustively codify the interfaces exposed by the classes: ensure that a predefined set of messages with a predefined set of parameters for each message (a.k.a methods) is permitted, control access to these methods and in the most extreme languages (e.g. Eiffel) even verify that object state and parameters conform to a predefined contract. This school is obviously not opposed to modifying behavior patterns based on runtime state, but it believes that the object system should not directly support that, and encourages programmers to implement their own mechanism on top of rigid-class object systems: this is what design patterns are usually meant to achieve.
The other school believes that an object system's first priority is giving objects absolute autonomy in parsing the messages they receive and as a result usually ends up with object systems that focus on powerful runtime extension and arbitrary message parsing functionality instead of compile-time strictness.
We could trace them back to their origins and call them the C++ school and the Smalltalk school. We could pin them to their modern champions and call them the Java school and the Ruby school. We could follow their poster boys and call them the class school and the message school. We could go on with type of languages they tend to thrive in and call them the dynamic school and the static school.
I would call either school "more object-oriented" than the other - they just have entirely orthogonal definition of what objects are. I can't even call either of them "better". I much prefer the Smalltalk school to the rigid version of the Java/C++ school (as was practiced in these two languages through most of the 90s and 2000s), but modern languages in this school have started incorporating some of the ML/Haskell tradition to give you much more powerful (and safer!) abstractions to define the way your objects may change their behavior in runtime. Design patterns are no longer encouraged, and if I'm using a language like Scala, Kotlin, Rust, Swift or even C#, I rarely find it necessary to employ a design pattern anymore.
A definition of objects that rules out immutable objects doesn't seem viable to me.
Furthermore, your definition merely requires mutability, which is a feature of Java and C++ objects, so I'm not sure what you think is lacking. Your definition reduces to "objects undergo state transitions".
Objects require at least an identity. Immutable data structures necessarily don't have identity (well, to say identity isn't meaningful because aliases can simply be seen as copies). There is more to it than identity, and you can always use a mutable variable or rebound parameter to recreate an identity for an immutable object (I.e. the world as an input and output).
I disagree with that and that particular paper. We must think of objects from a design perspective, not a technical one, and the one thing that distinguishes objects is their ability to be "things" rather than just constructs.
42 is not an object because the value can reproduced easily. John Smith, on the other hand, is not just a value, but has a name and an identity, is not exactly the same person everyday, and so on. 42 can be used in equational reasoning (it never lies) while John Smith cannot. John Smith, on the other hand, can be attributed with semi-autonomous behavior while such notion isn't meaningful for 42.
> We must think of objects from a design perspective, not a technical one
Why? Why should objects exclusively get this distinction when every other programming language paradigm is analyzed technically?
> 42 is not an object because the value can reproduced easily.
So?
What's the point of making "object" synonymous with "identity" when we already have the term "identity" to designate the relevant property?
Survey a bunch of programmers and ask if they agree with the phrase "objects respond to messages, and the only way to interact with an object is to send it messages". I'd wager a majority would agree with this statement. Which means procedural abstraction/encapsulation, wherein no details about object internals can leak to clients, is the only meaningful property of objects. 42 can then take on many representations, as long as they all respond to the same messages.
> Why? Why should objects exclusively get this distinction when every other programming language paradigm is analyzed technically?
Who is analyzing? I definitely don't analyze other paradigms technically. You should know me better than that. I'd rather all paradigms be analyzed based on how they are used to write programs and how they influence program designs. The technical details are just details.
> What's the point of making "object" synonymous with "identity" when we already have the term "identity" to designate the relevant property?
Again, it is how you design your programs: are you relying heavily on equational reasoning, or have you divided things into a bunch of nouns with behavior.
> "objects respond to messages, and the only way to interact with an object is to send it messages".
They might indeed, but it is a vacuous statement to begin with. It is extremely sad how little design is considered in programming languages. That an entire paradigm could be boiled down to such a true but useless statement.
> I definitely don't analyze other paradigms technically. You should know me better than that. I'd rather all paradigms be analyzed based on how they are used to write programs and how they influence program designs. The technical details are just details.
The technical details define how programs are structured. I don't see how you can separate these questions if that's what you mean by "design".
> Again, it is how you design your programs: are you relying heavily on equational reasoning, or have you divided things into a bunch of nouns with behavior.
Sure, but this doesn't preclude immutable objects from being used in subprograms that don't depend on state transitions. For instance, object factories may or may not be immutable, but if they are, they're suddenly not objects anymore? That distinction seems pointless.
> They might indeed, but it is a vacuous statement to begin with.
I agree that many people might take virtually anything to be a message, but when you try to specify with technical precision what that phrase actually entails, you get meaningful constraints, and I think Cook did a pretty good job distilling those properties.
For instance, "the only way to interact with an object is to send messages" means that no information can be revealed than what the object volunteers, ie. encapsulation, and this voluntary disclosure can't be captured statically to enforce stronger properties as with ADTs.
Design is like what does my program look like when I use X vs. what does it look like when I use Y? What are the implications of those two choices? I see objects and functions are pure design abstractions: you can easily emulate one or the other in a language that doesn't provide them as first class constructs, and even when they are provided as first class constructs, you can always use them in ways that are poor for the designs they support.
That "objects communicate via messages" leads to absurdities like numbers being considered as objects. It is completely blind to their benefits and drawbacks as anthropomorphic entities.
> That "objects communicate via messages" leads to absurdities like numbers being considered as objects.
It's only absurd if you're implicitly ascribing properties to objects that haven't been explicitly justified.
It seems perfectly reasonable to me that numbers would be objects, because numbers have a multitude of representations, all of which we'd like to be interchangeable. For instance, machine registers vs. arbitrary precision numbers. With ADTs, we'd have to explicitly define overloads for addition that handle the various permutations of parameters for addition of various number types, but a Cook-like object language wouldn't need this at all. Any representation of a number would be interoperable with any other implementation, automatically. That's neither vacuous or absurd, and these properties follow directly from taking that phrase describing objects to its logical conclusion.
When we come down to this, it is clear we just have very different of what objects are or are supposed to be. Interoperability (polymorphism) is at the center of the definition that you are using, and it is nowhere near as important in my definition of object, where polymorphism is possible with values and not related to their objecthood (it is a technical detail in how values can be made polymorphic).
We are talking past each other because we don't possess compatible meanings of the word object.
I don't think we're talking past each other, as I think we both understand the differences in each of our definitions, ie. I understand you think identity is central but I disagree, and you understand that I think procedural abstraction is central, but you disagree.
Where we differ is which definition ought to deserve the label "object", in the pure sense the way we have a definition for "function".
Even function has a definition in the philosophical sense unrelated to even the technical details of the lambda calculus. Especially when the function is pure and/or continuous, these are really useful distinctions in a design vs. using a bare bones definition that talks about a function being a pointer to some code.
> Even function has a definition in the philosophical sense unrelated to even the technical details of the lambda calculus.
Agreed, "function" has many concrete definitions, but arguably, there is some essence of what it means to be a function that we see reflected in each instance, and so virtually all of them are interconvertible in some real sense.
For "object", I see this essence as procedural abstraction. Every discussion of objects features access to data mediated by arbitrary code, which is how they implement behaviour. Object factories and numerical abstractions (fixed, rings, arbitrary precision, etc.) simply don't fit into your narrative of objects, so defining objects as requiring identity is immediately refuted in my mind.
Certainly you could argue that your approach to objects is better for software construction, but I don't think that means other approaches are no longer object-oriented, in the way that we can say that C permits you to violate the essence of functions.
I see objects as being completely unrelated to procedural abstraction, especially in its purest anthropomorphic form where objects have their own independent actuators and sensors (e.g. As in Kodu without any procedures at all, but also heavily used in etoys, scratch and of course Simula). In that case, they are just little entities (with identities of course) doing their own thing and communicating only through their environment.
It sounds like you're using "object" and "not an object" in the same way I would use "entity" and "value". I think this is Java terminology that gets used in C# sometimes too, I'm mostly thinking about NHibernate.
A value like 42 is fixed throughout time, it is always 42, and any 42 is indistinguishable from any other 42.
An entity on the other hand has an identity over time, so John Smith is always John Smith, even if he gets married and changes his name to John Brown.
Obviously it is confusing having different groups using the same words with different meanings, but that's just the way language works.
Language is always overloaded in the object-oriented sense. In the functional programming world, definitions are defined much more precisely, but then words have much less flexibility in how they can be used.
Modula-3 (although i'm not terribly familiar with the language) has partial revelation (a rather nuanced form of encapsulation), and branding where structural transparency does not preclude nominal identity... I tend to view the whole thing as a false dichotomy.
I'm not sure what you're disagreeing with specifically. "Partial revelation" sounds like a feature of ADTs they mixed into objects, which all modern OO languages have done, so I'm not sure what you think is the relevance to objects specifically.
If we're trying to characterize "objects" in its purest sense, it must have some all-encompassing property. Mixing in other properties for pragmatic reasons doesn't seem relevant here.
> that identity is not meaningful or useful for transparent immutable data
No one claimed it wasn't useful, simply that it's not necessary to have objects.
Exactly what I thought you were saying, and disagree with.
I consider it fine to say "Objects require identity",
without identity a fully encapsulated object is nothing more than an anonymous black hole.
When you turn this around to "Only objects require identity", or "identity is not meaningful for data", a hash code is one example of data which could benefit from identity, since you do not want to mix hash codes made by different hash functions. The positive integers 0,1,2... differentiated between the set of nonnegative integers are another simple
to preclude identity from data, is to preclude the ability to differentiate these on anything but the value level.
surely a simplification, but by no means necessary.
Hash functions on values don't depend on identity and collisions are accepted as part of hash's definition. A GUID (and MD 5 hashes) for all practical purposes are identities.
Identities really mess up equational reasoning which is why they are basically missing from pure functional languages like Haskell. A lot of clever solutions like FRP involve solving what are otherwise straightforward problems without resorting to the use of identity.
Apologies I meant the identity of the hash algorithm e.g. MD5, attached to the bits produced by running the hash algorithm on an object, while hash collisions are absolutely accepted, mixing hash values produced by different hash functions is generally frowned upon.
Identity at the structural level is very much either you care about the identity, or you do not (in which case "this one"...), maybe 99.9% of the time "this one" suffices, however the takeaway from this should be that identity of structurally transparent objects should be ignorable, rather than structurally transparent objects should not have identity should one care to acknowledge it.
I would say identity is core to objectness. Without identity, something is purely structural, only its value is relevant because it will not transition to another state later. Objects must be things you can manipulate. They have a presence beyond their values.
I use objects that can't transition from one state to another practically every working day. It's a strange view point to me that because something is immutable, it's not really an object. But a view worth thinking about.
To me the important thing is hiding internal state and sending high level messages instead of something like "change internal field x to y". Whether that internal state you're hiding is immutable or not seems irrelevant to me.
If you have a variable X that is pointing at different immutable maps over time, then X is the identity of your object that is implemented with multiple immutable maps.
Because objects are supposed to hide their internal state and the actions you do with them are heavily dependent on that state. This means that an object must have an identity to be sure you're working with the right object.
If you don't have identity it means that you must know yourself what state you're working with. This defeats the definition of objects that says that their state is hidden.
Encapsulation is not identity. Immutable objects are still perfectly encapsulated, but they don't have identity, which is what the OP was asking about.
I do come across the view a lot that immutable objects aren't really OO. It's something I really don't understand - state that changes over time doesn't seem like a key ingredient to me at all.
Erlang is an amazing language for learning how to effectively use OOP principles. I feel like anyone who works primarily with class-based languages owes it to themselves to write a few server-like modules in Erlang even if they never touch the language again.
Perl is also another interesting language for learning the low-level details of OOP, since its default set of OOP features (as of Perl 5; this all changes in Perl 6) involves manually "blessing" some data structure into an object. Objects are thus just "blessed" data structures (and not just hash tables, either, though those are the most common), and methods are just subroutines that accept their object as their first argument.
Between Perl and Erlang, I think I've ended up learning way more about real object orientation than I did from my dabbling in C++ and Java.
I have been thinking for a while that oop in the message passing sense is basically coroutines. I guess that is encoded in Erlang? Probably should learn it at some point.
>My definition of an object is "an ADT that gets to 'decide', using private runtime state, what to do in response to any/all attempts to interact with it." Which makes for a very simple test: if something is an object, you should be able to send it a message that makes it change its mind about what messages it accepts after that.
Your criterion doesn't match your definition.
Specifically "you should be able to send it a message that makes it change its mind about what messages it accepts after that" doesn't logically derive from "an object is an ADT that gets to 'decide', using private runtime state, what to do in response to any/all attempts to interact with it".
The second not only needs:
a) an object is an ADT that gets to 'decide', using private runtime state, what to do in response to any/all attempts to interact with it
but also implies b:
b) you can interact with the object and alter its private decision making process (and not just partially, e.g. only alter the data part of its runtime state).
Meh, that's a bit like arguing over "I could care less" vs "I couldn't care less", except far more pedantic because this time it's about an abstract concept that doesn't mean anything at its face value.
Object Oriented Programming is whatever the majority of people agree to it being, so as to facilitate communication. "Original definitions" don't matter. Alan Kay has no more right to define "Object Oriented Programming" than anyone else, you or me included, anymore than long-dead Thomas Jefferson has a greater right than the rest us to define the course of our country.
If you go on a job interview and they ask you "do you know object oriented programming?" and you say "oh yeah, I know all about that," then you're both going to be very disappointed on your first day of work.
Which makes for a very simple test: if something is an object, you should be able to send it a message that makes it change its mind about what messages it accepts after that.
There's no obvious way to implement a behavior like this using the native "objects" in nominally-object-oriented languages like C++ or Java. (But you can do so with the closure types from these languages. Or you can look higher on the stack: threads with IPC queues, or POSIX processes, are both objects.)
But implementing this behavior is obvious in Ruby; this is how basic things like Object#extend work.
And implementing this behavior is also obvious, oddly enough, in Erlang: http://joearms.github.io/2013/11/21/My-favorite-erlang-progr...
In conclusion, Erlang is more object-oriented than Java. ;)
---
And yeah, I know, this definition of OOP-ness sort of flies in the face of decades of things people have said about OOP. Objects, under this definition of OOP, don't obey any of the SOLID principles. They're slippery; unpredictable; "alive."
But this was the original definition! It's the one Smalltalk fits; it's the one LambdaMOO fits; it's what is meant by referring to Javascript as object-oriented.
It's a shame we have this collision between meanings; having two separate words for "OOP like Smalltalk" and "OOP like Java" would have saved language-designers all a world of arguments from people who try to propose extensions to a language in one category, assuming it's in the other.