> which started at the Norwegian Computing Center in the Spring of 1961
The date is wrong; only Simula 67 qualifies as an "object-oriented" language (the term was not used then though). The development of Simula 67 started in 1966. The author probably means the predecessor language, which did not have the properties associated with object orientation (moreover, it was a simulation language, whereas Simula 67 was designed as a general purpose programming language).
> Smalltalk-72 ... was clearly inspired by Simula, and took from Simula the ideas
of classes, objects, object references, and inheritance
Smalltalk-72 had no inheritance. Kay was aware of it but didn't like the idea. Only Ingalls added it to his Smalltalk-76 implementation.
People disagree widely on what OOP really is/means. I've read and been in many definition debates. Even defining what inheritance, polymorphism, and encapsulation is, is also a can of worms.
I think Alan Kay himself has suggested that what he really wanted when thinking of OOP was something more akin to the Actor model as espoused by Hewitt and realised by Erlang and to a lesser extent Scala on the JVM.
This model gives full benefit of FP at the actor level whilst being realistic about distributed messaging semantics.
I’ve always felt that it would be interesting to research processor cores that could better exploit the assumptions in this model, and this would be the future of both multi-core and distributed computing. Actor message queues would be in hardware, there would be opportunities to reduce context switching overhead between actor activations, memory models would be more fine grained, capability based security would be ingrained.
> I think Alan Kay himself has suggested that what he really wanted when thinking of OOP was something more akin to the Actor model as espoused by Hewitt and realised by Erlang and to a lesser extent Scala on the JVM.
> This model gives full benefit of FP at the actor level whilst being realistic about distributed messaging semantics.
It's interesting you bring that up; I normally am very much on the 'Use interfaces and Dependency injection instead of inheritance' camp. And yet, When I work with Actor Model frameworks (Mostly Akka/Akka.Net) I find places where using some simple inheritance is insanely powerful yet intuitive. You wind up getting something in-between OOP and AOP with the results.
It is also to note that features like closures and LINQ-like operations were already available in Smalltalk-80, which tend to be the most FP features used by average enterprise joe/jane developer.
Have a look at the paper. It's actually one of its conclusions that Simula had active objects even in 1961 and also in the OO version 1967, but for some reason this aspect didn't make it into the concept generally envisioned by OO today.
Here are some thoughts about OOP I had recently, maybe someone here had similar thoughts:
It seems to me that the biggest cause of maintenance problems in object-oriented programming is that it tries to manage global mutable states by partitioning and encapsulating all state into objects.
However, the only way to prevent two unrelated objects from manipulating the same part of the state is to create a strict dependency hierarchy or arborescence[0] between all objects in the system.
This combination of data and code in a strict hierarchy means that all data access patterns are baked into this dependency tree, which makes later, unforeseen changes to the software extremely difficult without taking shortcuts in the dependency tree and thereby destroying the encapsulation, which would destroy the whole point of OOP in the first place.
> his combination of data and code in a strict hierarchy means that all data access patterns are baked into this dependency tree
I believe OOP is really good at modelling state and really bad at modelling data.
With state (and side-effects modeled by state machines) you want a well-defined interface, messages, local retention and so on. This way you get the benefit of reasoning about state in a constrained manner both as the caller and the callee.
For example it makes sense to model Mario as a state-machine that receives messages from game-pad (pressed buttons) and collision events. Note that this is a conceptual, high level kind of modelling and not a concrete, structural one.
But data is about something. There is data about Mario at any given point in time: his physical dimensions, his velocity, consumed power ups and so on.
This is where OOP makes no sense at all. Reading and transforming data should be universal/uniform rather than guarded by idiosyncratic getters, setters and so on.
This is why SQL, JSON, XML, HTML, Unix filters, AWK, Functional Programming etc. are so powerful. These technologies provide/enable a uniform/universal way of reading data and composing transformations.
(As a side-note: I consider state that never leaks as an implementation detail, usually for performance)
That's a very interesting observation, that is very much aligned with my own views, but I'll need some time to absorb the details here.
I'm myself on the quest to fully understand what is really wrong with OOP and then try to share it with more people. It's actually very hard because it's a complex topic, ridden with subtleties, and even the exact definition of "what is OOP-code" is elusive. I've just recently made a reddit post that you (and other's who find your post interesting) might also enjoy: https://www.reddit.com/r/rust/comments/ju5oqp/how_to_avoid_r...
I wish there was some "OOP-deniers" community, where we could discuss things like that with an open mindset. Usually discussing OOP degenerates quickly and it's impossible to even establish some shared understanding.
I believe languages should allow the programmer to define the relationship between one code block and another. This includes scope and state relationship. Think more powerful lambdas with better interface syntax and options. OOP languages typically hard-wire such relationships into a limited set of relationship conventions. This results in OOP graphs that don't fit the domain or need well.
Sure, one can do such in Lisp, but many find Lisp hard to read. With C-style languages, the "ugly" syntax gives helpful visual cues. Stuff within (...) is usually a parameter list, stuff within {...} is usually sequential code, and "[...]" indicates indexing of some kind, for example. I find such helps my mind digest code faster.
Lisp aficionados will often claim "one gets used to" the uniformity. But that's like arguing we don't need color because one can "get used to" seeing in black and white. To a degree, yes, but the color adds another dimension of info. Easily recognizable group/block types is another color.
I think adhering to the Solid principles prevents those problems. Single-responsibility should prevent two things working on the same data, dependencies should only point to abstractions and if you feel that a class hierarchy is hard to change favor composition over inheritance.
By now, most of the classes I write are immutable and I use inheritance rarely (although I do use interfaces).
The one feature that I still find very useful from OOP is the ability to have a "shared context" for a related set of methods/functions. This works especially well with dependency injection: I can have some properties passed to the class constructor (say: the base directory where some files lie, a logger implementation, etc.) and then write my file manipulation functions without having to pass those properties around from function to function.
I wonder if there is something similar in some FP languages.
The reasoning behind "closures", is that one can capture some context such as the example you describe. If we think of an object as simply a record of functions, then we can close over some shared context during the construction of such records, if we have a language with records and closures.
Look up the Reader monad. Don't be intimidated by the M word, it's a simple pattern which make use of function arguments but masks the argument drilling.
Others have mentioned closures; there are also a few other patterns that can do similar things.
One is "ad-hoc polymorphism", which lets us associate values with types. This is essentially the same as implementing an interface in languages like Java, except we don't need to modify any existing definitions (e.g. we don't need to edit a class to say "extends Foo"). This is useful for picking between pre-defined sets of values, e.g. for your example of logger and base dir we can define an interface like 'Environment' with methods returning a base dir and a logger; then we can define a 'Production' type which implements this interface by providing the real logger and base dir, a 'Test' type which returns a temp dir and stdout logger, a 'Dev' type whose logger keeps verbose debug messages, etc. We can write our code in terms of a generic Environment, then in our 'main' function we specify that we're using 'Production'; in our test suite we specify that it's 'Test'; when debugging we can specify 'Dev'.
Another approach is called 'Reader', and it's based around the following type and helper functions (in Haskell syntax):
data Reader a b = R (a -> b)
read :: Reader a a
read = R (\x -> x)
runReader :: Reader a b -> a -> b
runReader (R f) x = f x
Or in Scala:
final class Reader[A, B](f: A => B) {
def run(x: A): B = f(x)
}
final object Reader {
def read[A]: Reader[A, A] = new Reader(x => x)
}
Note that this type literally just contains functions from some type A to another type B. The function 'read' is a wrapped-up identity function (returning its argument unchanged), the 'run' function just applies a wrapped-up function to an argument.
This gets interesting if we do two things. First we can specialise the input type (a or A), e.g. in Haskell:
type Application t = Reader (Path, Logger)
Or in Scala:
type Application[T] = Reader[(Path, Logger), T]
The type 'Application[T]' represents functions from a Path+Logger pair to a T.
Next we can define some functions for manipulating Reader values:
-- Applies a function f to a Reader's result; AKA function composition
map :: (a -> b) -> Reader t a -> Reader t b
map f (R r) = R (\x -> f (r x))
-- Wrap up a value into a Reader, by accepting an argument and ignoring it
wrap :: a -> Reader t a
wrap x = R (\y -> x)
-- Combine two Readers, so they're given the same argument and their return values are paired
product :: Reader t a -> Reader t b -> Reader t (a, b)
product (R f) (R g) = R (\x -> (f x, g x))
-- "Unwrap" nested Readers, by passing the argument into the result
join :: Reader t (Reader t a) -> Reader t a
join (R f) = R (\x -> runReader (f x) x)
Together these functions let us write arbitrary functions "inside" this 'Application' type. For example:
-- The 'fst' function gets the first element of a pair
baseDir :: Application Path
baseDir = map fst read
-- The 'snd' function gets the second element of a pair
logger :: Application Logger
logger :: map snd read
-- Use the logger to log the given string (via some 'logWith' function)
log :: String -> Application ()
log msg = map (\l -> logWith l msg) logger
-- Read the contents of a given file from the base dir
openFile :: Filename -> Application String
openFile f = map (\d -> readContents (d + "/" + f)) baseDir
-- Log the contents of a given file from the base dir
logContents :: Filename -> Application ()
logContents f = join (map log (openFile f))
Note how we can only access the base dir and logger from 'inside' the Application type; i.e. we can get an 'Application Logger', but we can never just get a 'Logger'. That's because the code doesn't actually define a logger or base dir anywhere; any value 'Application T' is really just a function '(Logger, Path) -> T'. To extract a result, we need to give it to 'runReader', which (in the case of Application) also needs to be given a '(Logger, Path)' pair.
Using these functions directly can be a bit tedious. Some FP languages have special syntax which makes it nicer, e.g. in Haskell we can write things like:
do x <- foo
y <- bar x
baz x y
The Scala equivalent is:
for {
x <- foo
y <- bar(x)
z <- baz(x, y)
} yield z
This API is a combination of Functor (map), Applicative (wrap and product) and Monad (join). Hence people sometimes call this "the Reader monad"; but don't let that scare you!
Initial hype. Then abuse and overuse. Disillusionment. Decline. At some point people will finally figure out what OOP is all about by looking up decades' old works and youtube videos (if youtube is still available) and hopefully will learn to integrate it.
*
A discussion with a dev recently when I started refactoring his PR with him and started pulling business logic from services into model.
Dev: "NOOO!!! You can't do this!!!"
Me: "But why?"
"You can't put code in model. Model is for fields and getters and setters only and business logic belongs in business services"
Me: "Why?"
Dev: blank stare... (imagined, this was over zoom)
Me: "So... why do you bother with having fields private if you plan to have public getters and setters for everything?"
Dev: blank stare
Me: "Isn't object oriented programming about having logic alongside the data rather than separate? Isn't it about modeling operations that act on the data to limit exposure to internal state?"
Just to set some context, I'm of the "OOP Generation" where I learned to program when OOP was at its zenith. Every major language was Class-based OOP (C++, C#, Java, Python, Ruby) and it was just THE way to code for all things in the foreseeable future.
That said, I've since looked into and learned other methods of programming: classic C, prototype-based JS, functional Clojure, and Go.
From what I can gather, even from the SOLID principles and other Class-based OOP advice (prefer composition over inheritence) is that Interfaces (as abstraction and reuse), Aggregation (that objects can have other objects), and Delegation (which allows of ergonomic aggregation) are the real winners of OOP.
In this way, Go gets it mostly right IMO, though at the risk of "initial hype" I'm still cautious on it.
It feels so backward that OOP is taught with a focus on abstract & subclasses, with very little emphasis on interfaces, which is really how we achieve the goals of abstraction and reuse.
What's funny is seeing languages like Java trying to "undo" the arguable "mistake" of abstract classes by adding more and more power into interfaces: default methods, etc.
To your original conversation, there's a mix going on with "model." Are they talking about the Service's API Model, the internal model representation, or the database model? The outer layers (API & Database) of your code should be plain structs (POJOs in Java), but you're absolutely right that within the business code itself, you should have logic within the classes. Otherwise, you're not coding much different than C passing around struct pointers.
No one, not even the supposed originators or the terms or the ideas, knows or can agree on what OOP even is or means.
So when codebases coded in the ubiquitous "OOP" style, (essentially procedural code full or state, mutation, temporal dependencies, side effects, exceptions and other things which make it impossible to reason about what's going on) are full of defects, the advocates can always claim it's because it's because it's not Real, True Object Oriented Programming.
Super proud to see work like this from Portland State! Without entering into the debate of Object Oriented Programming vs. Not OOP, I think legitimate research and examination is important for progress on both sides. Excited to do more than just skim this after work.
The paper is from 2012, and it is not actually about research results, but a historical outline, even with a few errors. But still interesting to read.
[+] [-] Rochus|5 years ago|reply
The date is wrong; only Simula 67 qualifies as an "object-oriented" language (the term was not used then though). The development of Simula 67 started in 1966. The author probably means the predecessor language, which did not have the properties associated with object orientation (moreover, it was a simulation language, whereas Simula 67 was designed as a general purpose programming language).
> Smalltalk-72 ... was clearly inspired by Simula, and took from Simula the ideas of classes, objects, object references, and inheritance
Smalltalk-72 had no inheritance. Kay was aware of it but didn't like the idea. Only Ingalls added it to his Smalltalk-76 implementation.
Not the only inaccuracies as it seems.
[+] [-] tabtab|5 years ago|reply
[+] [-] faichai|5 years ago|reply
This model gives full benefit of FP at the actor level whilst being realistic about distributed messaging semantics.
I’ve always felt that it would be interesting to research processor cores that could better exploit the assumptions in this model, and this would be the future of both multi-core and distributed computing. Actor message queues would be in hardware, there would be opportunities to reduce context switching overhead between actor activations, memory models would be more fine grained, capability based security would be ingrained.
[+] [-] valenterry|5 years ago|reply
Using actors is a great way to chunk the worlds into smaller parts, each of which can then be handled in fully FP manner.
[+] [-] to11mtm|5 years ago|reply
> This model gives full benefit of FP at the actor level whilst being realistic about distributed messaging semantics.
It's interesting you bring that up; I normally am very much on the 'Use interfaces and Dependency injection instead of inheritance' camp. And yet, When I work with Actor Model frameworks (Mostly Akka/Akka.Net) I find places where using some simple inheritance is insanely powerful yet intuitive. You wind up getting something in-between OOP and AOP with the results.
[+] [-] pjmlp|5 years ago|reply
[+] [-] Rochus|5 years ago|reply
[+] [-] taffer|5 years ago|reply
It seems to me that the biggest cause of maintenance problems in object-oriented programming is that it tries to manage global mutable states by partitioning and encapsulating all state into objects. However, the only way to prevent two unrelated objects from manipulating the same part of the state is to create a strict dependency hierarchy or arborescence[0] between all objects in the system.
This combination of data and code in a strict hierarchy means that all data access patterns are baked into this dependency tree, which makes later, unforeseen changes to the software extremely difficult without taking shortcuts in the dependency tree and thereby destroying the encapsulation, which would destroy the whole point of OOP in the first place.
[0] https://en.wikipedia.org/wiki/Arborescence_(graph_theory)
[+] [-] dgb23|5 years ago|reply
I believe OOP is really good at modelling state and really bad at modelling data.
With state (and side-effects modeled by state machines) you want a well-defined interface, messages, local retention and so on. This way you get the benefit of reasoning about state in a constrained manner both as the caller and the callee.
For example it makes sense to model Mario as a state-machine that receives messages from game-pad (pressed buttons) and collision events. Note that this is a conceptual, high level kind of modelling and not a concrete, structural one.
But data is about something. There is data about Mario at any given point in time: his physical dimensions, his velocity, consumed power ups and so on.
This is where OOP makes no sense at all. Reading and transforming data should be universal/uniform rather than guarded by idiosyncratic getters, setters and so on.
This is why SQL, JSON, XML, HTML, Unix filters, AWK, Functional Programming etc. are so powerful. These technologies provide/enable a uniform/universal way of reading data and composing transformations.
(As a side-note: I consider state that never leaks as an implementation detail, usually for performance)
[+] [-] dpc_pw|5 years ago|reply
I'm myself on the quest to fully understand what is really wrong with OOP and then try to share it with more people. It's actually very hard because it's a complex topic, ridden with subtleties, and even the exact definition of "what is OOP-code" is elusive. I've just recently made a reddit post that you (and other's who find your post interesting) might also enjoy: https://www.reddit.com/r/rust/comments/ju5oqp/how_to_avoid_r...
I wish there was some "OOP-deniers" community, where we could discuss things like that with an open mindset. Usually discussing OOP degenerates quickly and it's impossible to even establish some shared understanding.
[+] [-] tabtab|5 years ago|reply
Sure, one can do such in Lisp, but many find Lisp hard to read. With C-style languages, the "ugly" syntax gives helpful visual cues. Stuff within (...) is usually a parameter list, stuff within {...} is usually sequential code, and "[...]" indicates indexing of some kind, for example. I find such helps my mind digest code faster.
Lisp aficionados will often claim "one gets used to" the uniformity. But that's like arguing we don't need color because one can "get used to" seeing in black and white. To a degree, yes, but the color adds another dimension of info. Easily recognizable group/block types is another color.
[+] [-] tarcon|5 years ago|reply
[+] [-] jackthemuss|5 years ago|reply
[deleted]
[+] [-] Tainnor|5 years ago|reply
The one feature that I still find very useful from OOP is the ability to have a "shared context" for a related set of methods/functions. This works especially well with dependency injection: I can have some properties passed to the class constructor (say: the base directory where some files lie, a logger implementation, etc.) and then write my file manipulation functions without having to pass those properties around from function to function.
I wonder if there is something similar in some FP languages.
[+] [-] willtim|5 years ago|reply
[+] [-] nesarkvechnep|5 years ago|reply
[+] [-] maattdd|5 years ago|reply
[+] [-] chriswarbo|5 years ago|reply
One is "ad-hoc polymorphism", which lets us associate values with types. This is essentially the same as implementing an interface in languages like Java, except we don't need to modify any existing definitions (e.g. we don't need to edit a class to say "extends Foo"). This is useful for picking between pre-defined sets of values, e.g. for your example of logger and base dir we can define an interface like 'Environment' with methods returning a base dir and a logger; then we can define a 'Production' type which implements this interface by providing the real logger and base dir, a 'Test' type which returns a temp dir and stdout logger, a 'Dev' type whose logger keeps verbose debug messages, etc. We can write our code in terms of a generic Environment, then in our 'main' function we specify that we're using 'Production'; in our test suite we specify that it's 'Test'; when debugging we can specify 'Dev'.
Another approach is called 'Reader', and it's based around the following type and helper functions (in Haskell syntax):
Or in Scala: Note that this type literally just contains functions from some type A to another type B. The function 'read' is a wrapped-up identity function (returning its argument unchanged), the 'run' function just applies a wrapped-up function to an argument.This gets interesting if we do two things. First we can specialise the input type (a or A), e.g. in Haskell:
Or in Scala: The type 'Application[T]' represents functions from a Path+Logger pair to a T.Next we can define some functions for manipulating Reader values:
Together these functions let us write arbitrary functions "inside" this 'Application' type. For example: Note how we can only access the base dir and logger from 'inside' the Application type; i.e. we can get an 'Application Logger', but we can never just get a 'Logger'. That's because the code doesn't actually define a logger or base dir anywhere; any value 'Application T' is really just a function '(Logger, Path) -> T'. To extract a result, we need to give it to 'runReader', which (in the case of Application) also needs to be given a '(Logger, Path)' pair.Using these functions directly can be a bit tedious. Some FP languages have special syntax which makes it nicer, e.g. in Haskell we can write things like:
The Scala equivalent is: These get rewritten to (the equivalent of): This API is a combination of Functor (map), Applicative (wrap and product) and Monad (join). Hence people sometimes call this "the Reader monad"; but don't let that scare you![+] [-] andrewl|5 years ago|reply
http://www.cs.otago.ac.nz/staffpriv/ok/Joe-Hates-OO.htm
It was originally discussed here at:
https://news.ycombinator.com/item?id=19715191
[+] [-] nextos|5 years ago|reply
[+] [-] lmilcin|5 years ago|reply
Initial hype. Then abuse and overuse. Disillusionment. Decline. At some point people will finally figure out what OOP is all about by looking up decades' old works and youtube videos (if youtube is still available) and hopefully will learn to integrate it.
*
A discussion with a dev recently when I started refactoring his PR with him and started pulling business logic from services into model.
Dev: "NOOO!!! You can't do this!!!"
Me: "But why?"
"You can't put code in model. Model is for fields and getters and setters only and business logic belongs in business services"
Me: "Why?"
Dev: blank stare... (imagined, this was over zoom)
Me: "So... why do you bother with having fields private if you plan to have public getters and setters for everything?"
Dev: blank stare
Me: "Isn't object oriented programming about having logic alongside the data rather than separate? Isn't it about modeling operations that act on the data to limit exposure to internal state?"
Dev: blank stare
Me: sigh...
[+] [-] valbaca|5 years ago|reply
That said, I've since looked into and learned other methods of programming: classic C, prototype-based JS, functional Clojure, and Go.
From what I can gather, even from the SOLID principles and other Class-based OOP advice (prefer composition over inheritence) is that Interfaces (as abstraction and reuse), Aggregation (that objects can have other objects), and Delegation (which allows of ergonomic aggregation) are the real winners of OOP.
In this way, Go gets it mostly right IMO, though at the risk of "initial hype" I'm still cautious on it.
It feels so backward that OOP is taught with a focus on abstract & subclasses, with very little emphasis on interfaces, which is really how we achieve the goals of abstraction and reuse.
What's funny is seeing languages like Java trying to "undo" the arguable "mistake" of abstract classes by adding more and more power into interfaces: default methods, etc.
To your original conversation, there's a mix going on with "model." Are they talking about the Service's API Model, the internal model representation, or the database model? The outer layers (API & Database) of your code should be plain structs (POJOs in Java), but you're absolutely right that within the business code itself, you should have logic within the classes. Otherwise, you're not coding much different than C passing around struct pointers.
Related terms: MVC, Data access object
[+] [-] dennisgorelik|5 years ago|reply
1) Keep your business logic simpler.
2) Keep your data model simpler.
If you put both business logic and data model in the same class, then:
- When you investigate data references pointing to your class -- you will see references noise from your business logic.
- When you investigate business logic references pointing to your class -- you will see references noise from your data model.
3) Combined "business logic + data model" class is much harder to refactor.
So, technically, you can combine business logic and data model in the same class.
But practically, such code combining will significantly complicate maintainability of your code.
[+] [-] unknown|5 years ago|reply
[deleted]
[+] [-] Rochus|5 years ago|reply
[+] [-] testtest1929|5 years ago|reply
[+] [-] bedobi|5 years ago|reply
So when codebases coded in the ubiquitous "OOP" style, (essentially procedural code full or state, mutation, temporal dependencies, side effects, exceptions and other things which make it impossible to reason about what's going on) are full of defects, the advocates can always claim it's because it's because it's not Real, True Object Oriented Programming.
[+] [-] bird_monster|5 years ago|reply
[+] [-] Rochus|5 years ago|reply
[+] [-] unknown|5 years ago|reply
[deleted]
[+] [-] lincpa|5 years ago|reply
[deleted]