What you're asking is basically the difference between an object-oriented data abstraction and data abstraction with abstract data types (ADTs, not to be confused with algebraic data types, which are also often abbreviated as "ADT"). (See About the understanding of data abstraction, revised by William R. Cook , his Proposal for simplified, modern definitions of "Object" and "Object-Oriented" and Object-Oriented Programming versus abstract data types .)
Two different instances of the same abstract data type can verify each other's representation (but not the presentation of instances of other abstract data types), while an object can never check another representation of an object, even if the object is an instance of the same type. Cook identifies this property as the fundamental and defining characteristic of an object-oriented data abstraction (and in fact, object-oriented in general) and calls it Autognosis (Self-Knowledge). I prefer the term Xenoagnosis (Foreign-Not-Knowledge), which was used either by Glenn Vanderburg, Rick de Natale, or the late Jim Weirich, IIRC, because it emphasizes the difference between the two approaches, namely, that objects cannot know about other objects.
In Scala, as you have noticed, object-oriented data abstraction can be achieved using the private[this]
access modifier.
class TestPrivate { private val notReallyPrivate = 42 private[this] val privateIMeanIt = 23 def blowUp(other: TestPrivate) = { other.notReallyPrivate other.privateIMeanIt } } // error: value privateIMeanIt is not a member of TestPrivate
In Erlang, the equivalent of Object is a process, and processes are fully encapsulated by the semantics of the language (they even have their own assembly, collected using garbage). In the end, they can even live on another machine on another continent, so stripping a performance is simply impractical. You send a message to the process, and the process is completely autonomous in deciding what to do with the message and how to respond to it. Mostly private[this]
.
In Clojure, object-oriented data abstraction can be achieved through Closures. (This is true for almost any language with closures and higher order routines, for example, you can also use functions as objects in Erlang instead of processes). Think about ECMAScript: although it vaguely has a language constructor called an “object”, ECMAScript objects are actually implemented using functions. You can implement them in Clojure the same way. An object-oriented form of data abstraction is also called procedural data abstraction, because instead of relying on types, it relies on hiding data behind a procedural interface (or a functional interface, if you want, after all, reciprocity and side effects are orthogonal to OO). Cook claims (half-jokingly) that since -calculus has only functions, the whole abstraction is functional, and therefore -calculus is the first, oldest and purest OO language - obviously, this means that the OO Data abstraction should be possible in languages that are closely associated with a stone, such as the Lisp family. (Historical anecdote: The scheme was originally intended to study OO and Actors, they only noticed during implementation that the interpreter code paths for sending messages and calling functions were identical.)
In languages such as Java or C♯, Object-Orientation is achieved using the Discovery Type System and Programmer, using only the interface
as types. interface
cannot describe the view, so if you make sure (through programming discipline, coding styles, code reviews, possibly static analysis tools) that only interface
ever used as types (each local variable, field, method parameter, return value method, casting operator, instanceof
checking, and the argument of the general type should always be interface
), and class
es are used only as factories (the only place where it is allowed to display the class name to the new
keyword), then you have reached the object-orientation data abstraction. (There are some other caveats; the most important thing is that you should not use referential equality.)
interface ITestPrivate { default int thisIsPublic() { return 0; } default void blowUp1(ITestPrivate other) { other.thisIsPublic(); other.privateIMeanIt(); } } class TestPrivate implements ITestPrivate { private int privateIMeanIt() { return 23; } void blowUp2(TestPrivate other1, ITestPrivate other2) { other1.notReallyPrivate();
Doing this should also be possible in F♯, since F♯ supports the interface
as well .