Why OO Sucks by Joe Armstrong (2000)
cs.otago.ac.nz
cs.otago.ac.nz
..."I wrote a an article, a blog thing, years ago - Why object oriented programming is silly. I mainly wanted to provoke people with it. They had a quite interesting response to that and I managed to annoy a lot of people, which was part of the intention actually. I started wondering about what object oriented programming was and I thought Erlang wasn't object oriented, it was a functional programming language.
Then, my thesis supervisor said "But you're wrong, Erlang is extremely object oriented". He said object oriented languages aren't object oriented. I might think, though I'm not quite sure if I believe this or not, but Erlang might be the only object oriented language because the 3 tenets of object oriented programming are that it's based on message passing, that you have isolation between objects and have polymorphism.
Alan Kay himself wrote this famous thing and said "The notion of object oriented programming is completely misunderstood. It's not about objects and classes, it's all about messages". He wrote that and he said that the initial reaction to object oriented programming was to overemphasize the classes and methods and under emphasize the messages and if we talk much more about messages then it would be a lot nicer. The original Smalltalk was always talking about objects and you sent messages to them and they responded by sending messages back."
See https://www.infoq.com/interviews/johnson-armstrong-oop (2010) for the full answer (and more), it's worth a read.
On the one hand, "object" can mean a unification of data structures with the procedures that act on them. In this view, the ideal is for everything to be an "object", and for all the procedures to actually be methods of some class. This is the place from which we get both the motivation for Java's ban on functions that don't belong to classes, and the criticism of Java as not being truly OO because not every type is an object. In this view, Erlang is not OO, since, at the root, functions are separate from datatypes.
On the other hand, "object" can describe a certain approach to modularity, where the modules are relatively isolated entities that are supposed to behave like black boxes that can only communicate by passing some sort of message back and forth. This ends up being the motivation for Java's practice of making all fields private, and only communicating with them through method calls. In this view, Erlang is extremely OO, for all the reasons described in parent.
I haven't done an exhaustive analysis or anything, but I'm beginning to suspect that most the woes that critics commonly describe about OO come from the conflation of these two distinct ideas.
That said, I would also argue that, like "object", "inheritance" is a word that can describe many distinct concepts, that and here, too, OOP's intellectual traditions create a muddle by conflating them.
Inheritance is a limited convention to do mixins. Including it in the abstract idea of object oriented programming is harmful, other than in reference to the ugly history of "Classical OOP" or "Non-Kay OOP" as you like.
I don't think it is.
Yes, it can get some people to just lash out in response.
But it also often forces people to think critically about how to convincingly justify their own standpoint to counter the provocation. This can be particularly useful when a viewpoint has "won" to the extent that people just blindly adopt it without understanding why.
It does have it's problems in that it is hard to predict, and there's a risk that measured reactions gets drowned out by shouting, so I'm not going to claim it's a great approach, but it has it's moments.
Like the blind acceptance of OOP religion (not the message passing kind), since the 90s
It is!
In my view, the point that Alan Kay and Joe Armstrong are trying to make is that languages like C++/Java/C# etc have very limited message passing abilities.
Alan Kay uses the term "late binding". In Kay's opinion, "extreme late binding" is one of the most important aspects of his OOP [1], even more important than polymorphism. Extreme late binding basically means letting the object decide what it's gonna do with a message.
This is what languages like Objective-C and Ruby do: deciding what to do after a method is dispatched always happen during runtime. You can send a message that does not exist and have the class answer to it (method_missing in Ruby); you can send a message to an invalid object and it will respond with nil (Objective-C, IIRC); you can delegate everything but some messages to a third object; you can even send a message to a class running in other computer (CORBA, DCOM).
In C++, for example, the only kind of late binding that you have is abstract classes and vtables.
-
> Or is it that "true OO" must be asynchronous?
It doesn't have to be asynchronous, but in Alan Kay's world, the asynchronous part of messaging part should be handled by that "dispatcher", rather than putting extra code in the sender or the receiver.
I don't remember Alan Kay elaborating on it, but he discusses a bit about this "interstitial" part of OOP systems in [2]
-
> In C++, for example, the only kind of late binding that you have is abstract classes and vtables.
That's not true, you can always have a "send_message(string id)". Few people do it because you lose static type safety. And some languages, like C# and Scala, have dynamic types that allows for the "method_missing" protocol and such features are very unpopular.
To be honest I don't see much of a difference. I've worked with a lot of dynamic OOP languages, including with Erlang-style actors and I've never seen the enlightenment of dynamic OOP message passing.
And I actually like OOP, but I don't really see the point of all this hyperbole about Smalltalk.
That is the difference. If every class in C++ had only one method - send_message and each object is an independent thread, you will get how Erlang works. That is how you would do the actor model in C++.
Inheritance, Polymorphism is emphasised in Java, C++ and C#, whereas Functional programmers emphasise function objects / lambdas / Command Pattern where you just have one method - calling the function. Infact having just method you no longer need Polymorphism / Interfaces.
C++'s vtables are determined at compile time. The specific implementation executed at a given moment may not be possible to deduce statically, but the set of possible methods is statically determined for every call site: It consists of the set of overridden implementations of the method with that name in the class hierarchy from the named type and downwards.
No such restriction exists in Ruby or Smalltalk or most other truly dynamic languages. E.g. for many Ruby ORM's the methods that will exist on a given object representing a table will not be known until you have connected to the database and read the database schema from it, and at the same time I can construct the message I send to the object dynamically at runtime.
Furthermore the set of messages a given object will handle, or which code will handle it can change from one invocation to the next. E.g. memoization of computation in Ruby could look sort-of like this:
class Memo
def method_missing op
result = ... execute expensive operation here ...
define_singleton_method(op) { return result }
end
end
After the first calculation of a given operation, instead of hitting method_missing, it just finds a newly created method returning the result."Extreme late binding" is used exactly because people think things like vtables represent late-binding, but the ability to dynamically construct and modify classes and methods at runtime represents substantially later binding.
E.g. there's no reason why all the code needs to be loaded before it is needed, and methods constructed at that time And incidentally this is not about vtables or not vtables - they are an implementation detail. Prof. Michael Franz paper on Protocol Extension [1] provided a very simple mechanism for Oberon that translates nicely to vtables by dynamically augmenting them as code is loaded at runtime. For my (very much incomplete) Ruby compiler, I use almost the same approach to create vtables for Ruby classes that are dynamically updated by propagating the changes downwards until it reaches a point where the vtable slot is occupied by a different pointer than the one I'm replacing (indicating the original method has been overridden). Extending the vtables at runtime (as opposed to adding extra pointers) would add a bit of hassle, but is also not hard.
The point being that this is about language semantics in terms of whether or not the languages allows changing the binding at runtime, not about the specific method used to implement the method lookups semantics of each language - you can implement Ruby semantics with vtables, and C++ semantics by a dictionary lookup. That's not the part that makes the difference (well, it affects performance)
> That's not true, you can always have a "send_message(string id)". Few people do it because you lose static type safety. And some languages, like C# and Scala, have dynamic types that allows for the "method_missing" protocol and such features are very unpopular.
If you're working in a language with static typing you've already bought into a specific model; it's totally unsurprising that people who have rejected dynamic typing their language choice will reject features of their statically typed language that does dynamic typing. I don't think that says anything particularly valuable about how useful it is. Only that it is generally a poor fit for those types of languages.
[1] Protocol Extension: A Technique for Structuring Large Extensible Software Systems, ETH Technical Report (1994) http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.42....
Completely independent objects passing messages and entirely parallelizable.
https://www.quora.com/What-is-the-difference-between-Alan-Ka...
I keep meaning to give Erlang a try, but just haven't had a reason yet. I do a lot of Clojure, these days :)
Some things that come to mind:
- Abstract classes/methods, and interfaces. This is implemented using vtables in C++.
- Ability to send messages asynchronously, or to other computers, without exposing the details of such things. You just call a method in another class and let your dispatcher handle it. There was a whole industry built around this concept in the 90s: CORBA, DCOM, SOAP. And Erlang, of course, in a different way.
- Ability to change the class/object during runtime. Like you can with Javascript and Lua, calling `object.method = `. Javascript was inspired by Self (a dialect of Smalltalk), so there's that lineage. Other languages like Python and Ruby allow it too.
- Ability to use the message passing mechanism to capture messages and answer them. Similar to Ruby's "method_missing" and ES6 Proxies in Javascript. This is super useful for DSLs and a great abstraction to work with. Check this out: http://npmjs.com/package/domz
Remember that you can have some of those things without dynamic typing (Objective-C).
a) The crash is from the default unhandled exception handler, which will send a signal to abort. So if you just want to crash, you can either handle that particular exception or install a different unhandled exception handler
b) An object gets sent the -forwardInvocation: message when objc_msgSend() encounters a message the object does not understand. The exception above gets raised by the default implementation of -forwardInvocation: in NSObject.
o := NSObject new.
o class
-> NSObject
n := NSInvocation invocationWithTarget:o andSelector: #class
n resultOfInvoking class
-> NSObject
o forwardInvocation:n
2019-04-22 07:49:12.339 stsh[5994:785157] exception sending message: -[NSObject class]: unrecognized selector sent to instance 0x7ff853d023c0 offset: {
(This shows that -forwardInvocation: in NSObject will raise that exception, even if the NSInvocation is for a message the object understands)If you override -forwardInvocation:, you can handle the message yourself. In fact, that is the last-ditch effort by the runtime. You will first be given the chance to provide another object to send the message to ( - (id)forwardingTargetForSelector:(SEL)aSelector; ) or to resolve the message in some other way, for example by installing the method ( + (BOOL)resolveInstanceMethod:(SEL)sel; )[0].
Cocoa's undo system is implemented this way[1], as is Higher Order Messaging[2][3]
[0] https://developer.apple.com/documentation/objectivec/nsobjec...
[1] https://developer.apple.com/documentation/foundation/nsundom...
Consider the following Ruby code:
class MyClass
def foo
'bar'
end
end
class MyClass
def method_missing(name, *args, &block)
if name == :foo
return 'bar'
end
super
end
end
To the outside observer, the two classes are effectively equivalent. Since, conceptually, a caller only sends a message `foo`, rather than calling a method named `foo`, the two classes are able to make choices about how to handle the message. In the first case that is as simple as invoking the method of the same name, but in the second case it decides to perform a comparison on the message instead. With reception of a message, it is free to make that choice. To the caller, it does not matter.If the caller dug into the MyClass object, found the `foo` function pointer, and jumped into that function then it would sidestep the message passing step, which is exactly how some languages are implemented. In the spirit of OO, I am not sure we should consider such languages to be message passing, even though they do allow methods to be called.
And languages that allow programmers to bypass OO with jmp instructions seem multiparadigm rather than not-OO...
Not unreasonable at all! In fact the term used in objective C and Ruby is “selector”. Beneath the synchronous veneer anyway.
class A
def foo; end
end
class B < A
def foo; end
def bar; end
end
Now you make a vtable for class A that looks conceptually something like this: slot for foo = address_of(A#foo)
slot for bar = method_missing_thunk(:bar)
And a vtable for class B that looks like this: slot for foo = address_of(B#foo)
slot for bar = address_of(B#bar)
The point being that you can see every name used in a method call statically during parsing, and can add entries like `method_missing_thunk(:bar)` to the vtable, that just pushes the corresponding symbol onto the stack and calls a method_missing handler that tries to send method_missing to the objects.You still need to handle #send, but you can do that by keeping a mapping of symbols => vtable offset. Any symbol that is not found should trigger method_missing; that handles any dynamically constructed names, and also allows for dynamically constructed methods with names that have not been seen as normal method calls.
When I started experimenting with my Ruby compiler, I worried that this would waste too much space, since Ruby's class hierarchy is globally rooted and so without complicated extra analysis to chop it apart every vtable ends up containing slots for every method name seen in the entire program, but in practice it seems like you need to get to systems with really huge amounts of classes before it becomes a real problem, as so many method names gets reused. Even then you can just cap the number of names you put in the vtables, and fall back to the more expensive dispatch mechanism for methods you think will be called less frequently.
(redefining methods works by propagating the new pointer downwards until you find one that is overridden - you can tell it's overridden because it's different than the pointer at the site where you started propagating the redefined method downwards; so this trades off cost of method calls with potentially more expensive method re-definition)
Getters/setters are technically message-passing methods, but they undermine the design goal because they more or less directly expose internal state to the public world.
But we see getters/setters used constantly. People don't use OO in the way Kay intended. Yes, methods are the implementation of the whole "message passing" thing Kay was talking about, but we see them used in ways he did not intend.
I wonder if we had different syntax for those cases we'd have less of them.
But then, again, it's very convenient to be able to add a method to a class that was previously a dumb struct.
No, they don't, because “more or less” is not actually directly. Particularly, naive getters and setters can be (and often are) replaced with more complex behavior with no impact to consuming code because they are simply message handlers, and they abstract away the underlying state.
I disagree.
Consider a `Counter` class, intended to be used for counting something. The class has one field: `Counter.count`, which is an integer.
A setter/getter for this field would be like `Counter.setCount(i: Int)` and `Counter.getCount() -> Int`. There is no effective difference between using these methods and having direct access to the internal state of the object.
A more "true OOP" solution would be to use methods with semantic meaning, for example: `Counter.increment()`, `Counter.decrement()`, and `Counter.getCount() -> Int`. (Yes, the getter is here because this is a simple example.) These kinds of methods are not directly exposing the internal state of the object to be freely manipulated by the outside world.
If your getter/setter does something other than just get/set, then it's not really a getter/setter anymore — it's a normal method that happens to manipulate the state, which is fine. But using getters/setters (in the naive, one-line sense) is commonplace with certain people, and I feel that their use undermines the principles Kay was getting at.
This has always been my problem with getters and setters. It's a way of either pretending you are not or putting bandaids on the fact that you're messing with the objects internal state. For objects with dynamic state this is really bad. The result is racy or brittle.
If they do, that's your fault for letting them. I guess you mean when people chain stuff thus
company.programmers.WebDevs.employ('fred')
where .programmers and .WebDevs is an exposed internal of the company and programmers department respectively? (I've seen lots of this, and in much longer chains too. We all have). In which case please see the Principle of Demeter <https://en.wikipedia.org/wiki/Law_of_Demeter> which says don't do this. Wiki article is good.
I doubt any language can prevent this kind of 'exposing guts' malpractice, it's down to the humans.
I remember reading that Alan Kay said when he saw the Linda model (<https://en.wikipedia.org/wiki/Linda_(coordination_language)>) he said it was closer to what he wanted smalltalk to be.
Actually, true OOP languages do prevent this. Internal state is completely private and cannot be exposed externally. The only way to interact with an object's state is through its methods — which means the object itself is responsible for knowing how to manipulate its internal state.
Languages like Java are not "true" OOP in this sense, because they provide the programmer with mechanisms to allow external access to internal state.
Internal state should be kept internal. You shouldn't have a class `Foo` with a private internal `.bar` field and then provide public `Foo.getBar()` and `Foo.setBar()` methods, because you may as well just have made the `.bar` field public in that case.
Also, FWIW, I did not downvote you. I dunno why you were downvoted. Seems you had a legitimate point here, even if I disagree with it.
Java and C++ way of message passing both stripped that medium down to a simple vtable to look up what methods the object has. Erlang and go have the right idea of passing messages through a medium that can serialize and multiprocess it. C# tries to do with further abstractions like parallelized linq queries and C#, python and nodejs use async/await to delegate the messages to event queues. Python can also send messages to multiple processes. All this shows us that message passing requires a medium that primitive method calls lack.
If they use vtable it'd be just slow. Not needing the trampoline and ability to inline harder is what makes it fast. The usual case is class hierarchy analysis,static calls (no more than a single implementer proven by the compiler), guarded calls (check +inline, java deoptimizes, if need be), bi-morphic call site inline, inline caches and if that fails - the vtable thing.
Message passing in a classical way is just awfully slow for a bottom of the stack building block. It doesn't map to the hardware. It does makes sense for concurrency with bounded, lock free queues (actor model). But at some point, someone has to do the heavy lifting.
1. You can send any message to any object. In case the object does not have a suitable handler, you will get an exception: <object> does not understand <message>. The whole thing is very dynamic.
2. There is no `static` BS like in c# or java. This is because each method has to be a method of an object. For each class there is a metaclass which is an object too, see: https://en.m.wikipedia.org/wiki/Metaclass#/media/File%3ASmal...
But not in the way he's describing OO in his blog post. He's talking about a language with functions bound to objects and where objects have some internal state. The OO he's describing does not have isolation between objects because you can share aliases freely; references abound.
Until some central body is officially appointed definition duty, the definition debate will rage on.
The only real problem I see is that too many technologist insist that there is 'one definition to rule them all' and it's usually the one they most agree with. As long as we all understand that these terms are fluid and can explain the pro's and con's of our particular version we will be fine.
Not the best moment of these otherwise two bright minds.
He coined the term “object,” but what he meant by a computational object was different than what it came to mean: a data structure with associated operations upon it. Kay meant a parallel thread of execution which was generally sitting in a waiting state—one could make a very strong analogy between Smalltalk's vision of “objects” and what we call today “microservices,” albeit all living within the same programming language as an ecosystem rather than all being independent languages implementing some API.
But whether this is an “object-oriented” vision depends on whether you think that an object is intrinsically a data structure or an independent computer with its own memory speaking a common API. The most visible difference is that in the latter case one object mutating any other object's properties is evil—it is one computer secretly modifying another’s memory—whereas in the other case it is shrug-worthy. But arguably the bigger issue is philosophical.
That is hard to explain and so it might be best to have a specific example. So Smalltalk invents MVC and then you see endless reinventions that call themselves MVC in other languages. But most of these other adaptations of MVC have very object-oriented models: they describe some sort of data structure in some sort of data modeling language. But that is not the “object” understanding of a model in Smalltalk. When Smalltalk says “model” it means a computer which is maintaining two things: a current value of some data, and a list of subscribers to that value. Its API accepts requests to create/remove subscriptions, to modify the value, and to read the value. The modifications all send notifications to anyone who is subscribed to the value. There is not necessarily anything wrong with data-modeling the data, but it is not the central point of the model, which is the list of subscribers.
A more extreme example: no OOP system that I know of would do something as barbarous as to implement a function which would do the following:
> Search through memory for EVERY reference to that object, and replace it with a reference to this object.
That just sounds like the worst idea ever in OOP-land; my understanding of objects is as data structures which are probably holding some sort of meaningful data; how dare you steal my data structure and replace it with another. But Smalltalk has this; it is called Object.become. If you are thinking of objects as these microservicey things then yeah, of course I want to find out how some microservice is misbehaving and then build a microservice that doesn't misbehave that way and then eventually swap my new microservice in for the running one. (That also hints at the necessary architecture to do this without literally scanning memory: like a DNS lookup giving you the actual address, every reference to an object must be a double-star pointer under the hood.) And as a direct consequence, when you are running Smalltalk you can modify almost every single bit of functionality in any of the standard libraries to be whatever you need it to be, live, while the program is running. Indeed the attitude in Smalltalk is that you will not write it in some text editor, but in the living program itself: the program you are designing is running as you are writing it and you use this ability to swap out components to massage it into the program that you need it to become.
This is partly because the term at the time meant a patch of storage with multiple data fields -- like a punched card image in storage or a Sketchpad data-structure.
But my idea was about "things" that were like time-sharing processes, but for all entities. This was a simple idea that was catalyzed by seeing Sketchpad and Simula I in the same week in grad school.
The work we did at Parc after doing lots of software engineering to get everything to be "an object", was early, quite successful, and we called it "object-oriented programming".
I think this led to people in the 1980s wanting to be part of this in some way, and the term was applied in ways that weren't in my idea of "everything from software computers on a network intercommunicating by messages".
I don't think the term can be rescued at this point -- and I've quit using it to describe how we went about doing things.
It's worth trying to understand the difference between the idea, our pragmatic experiments on small machines at Xerox Parc, and what is called "OOP" today.
The simplest way to understand what we were driving at "way back then" was that we were trying to move from "programming" as it was thought of in the 60s -- where programs manipulated data structures -- to "growing systems" -- like Smalltalk and the Internet -- where the system would "stay alive" and help to move itself forward in time. (And so forth.)
The simplest way to think about this is that one way to characterize systems is that which is "made from intercommunicating dynamic modules". In order to make this work, one has to learn how to design and maintain systems ...
Joe Armstrong was criticizing C++-style OOP when he wrote his critique.
After he learned more about Alan Kay's view on OOP, he decided that Erlang is closer to Alan Kay's OOP and he approves that specific flavor of OOP.
He didn't change his stance based on popularity. He changed his stance because in the 80s/90s the term "OOP" was synonymous with C++-style-OOP, but that changed in the 2000s thanks to 1) C++-style OOP criticism became commonplace in our industry (thanks to people like Joe Armstrong) and 2) an increase of popularity languages like Ruby and Objective-C (which are closer to Smalltalk) and even much-maligned concepts such as DCOM, SOA and CORBA.
> Objection 1. Data structure and functions should not be bound together
Well, in my experience, in every almost every code-base (either from functional, or imperative programing), we end up with modules, witch are a set of function taking the same type as a parameter. This is very close to binding the functions and the types...
> Objection 2. Everything has to be an object.
I don't get the example. The only thing that this show, is the benefits of having a range type built in the language. Then it's just type aliases.
"There are no associated methods.", yes, but you will need functions to manipulate those types (just translate one type into another), at the end, it's going to a module, which is almost an object.
> Objection 3. In an OOPL data type definitions are spread out all over the place.
That's true. It also makes thinking about the data layout complex. That's why other paradigm have been developed (DOP), on top of OOP. Now you can also think that having those defined together makes dependency management easier.
> Objection 4. Objects have private state.
False. Objects can have a private state. This a problem with mutability, not oriented object programing. You can have non mutable OOP.
> Why was OO popular?
>> Reason 1. It was thought to be easy to learn.
The past 20 years have shown how easy it is. In fact, I actually think it's too easy, people rely too much on abstraction, without even trying to understand what's going on. I my opinion, it promotes a lazy mindset (This is my biggest criticism about OOP).
>> Reason 2. It was thought to make code reuse easier.
I would like an evidence that it's not.
>> Reason 3. It was hyped.
True, but that does not make it bad. People tried to hype every technologies... Some stayed, some went away.
>> Reason 4. It created a new software industry.
How has OOP created a software industry that would not have existed if functional programing had "won the fight"?
> Well, in my experience, in every almost every code-base (either from functional, or imperative programing), we end up with modules, witch are a set of function taking the same type as a parameter. This is very close to binding the functions and the types...
There is a key distinction: If I have two subsystems that use the same data in different ways, I can keep those concerns separate by putting the functions for each concern into a different module. Binding all the functions to the type mixes the concerns together and creates objects with way too much surface area.
Also, most OO langs make a big ceremony out of each new type: create the class file, create the test file, blah blah blah. I want types to be cheap so I can make them easily and capture more meaning with less work.
Appreciate it :)
> There is a key distinction: If I have two subsystems that use the same data in different ways, I can keep those concerns separate by putting the functions for each concern into a different module. Binding all the functions to the type mixes the concerns together and creates objects with way too much surface area.
This is where composition helps. Now, historically, indeed OOP programmers have not been the best at using composition. Now, looking at more recent projects, this has got a lot better.
> Also, most OO langs make a big ceremony out of each new type: create the class file, create the test file, blah blah blah. I want types to be cheap so I can make them easily and capture more meaning with less work.
Totally agree with that, the ability to define a type in one line and have it reflected though the entire code base through type inference is the one thing that I miss the most in C/C++.
It does, though in my experience it leads you down a path that ends in some pretty strange names, as you nominalise more and more nebulous concepts, trying to verb in the kingdom of nouns.
FWIW, I think that this is what distinguishes object-oriented programming as a language paradigm from object-oriented programming as a design paradigm: If you're going to say that all data types should have the operations you can perform on them bound up together into a single class (or class cluster), then that would imply that small, cheap data storage types are expected to be few in number.
If, OTOH, it's more about modularity, and you're not so concerned about how things happen on the sub-module level, then that gives more ideological space for code that's, for example, functional in the small scale and object-oriented in the large scale, like Erlang. Or procedural in the small scale and object-oriented in the large scale, like some C++ code.
>> Reason 2. It was thought to make code reuse easier. > I would like an evidence that it's not.
Mainstream OOP approaches achieve better cohesion by coupling data structures to functions. In the worst case you end up with essentially modules that contain "global variables" local to that module. In other words the only reason to have your instance variables is to remove the need to pass those variable to the functions as parameters.
This hurts the ability to write generic code. In fact you see this problem all the time in OO code. You have a base class and a bunch of basically unrelated child classes. It's not so much that the child ISA base, it's more that the child ACTS_AS_A base. But then, you run into all sorts of problems because one child (because it is using very different data structures) requires specialised code.
There are ways of getting around this, but often those ways end up encouraging you to implement an alphabet soup of design patterns that interact with each other -- causing more coupling rather than less. All for the want of a generic function.
IMHO OO is actually a poor vehicle for achieving code reuse. In fact, aiming towards this goal is usually one of the root causes I find in really poor OO designs. What OO is really good at is separating concerns and building highly cohesive code. This sometimes comes at the cost of increased coupling which inherently reduces reusability. I don't actually think that's a bad thing when used appropriately, but the old school "OO creates reusable code" is just a bad idea IMHO. It's the kind of thing that several of us threw out the window in the 90's along with large inheritance hierarchies -- nice idea, but didn't work out in practice.
Wouldn't this violate the "encapsulation" pillar of OOP? As far as I know, it's always taught with encapsulation, inheritance, polymorphism being its three pillars.
> How has OOP created a software industry that would not have existed if functional programing had "won the fight"?
I'm not sure functional programming has lost yet. I haven't worked with it personally, and so can't speak to its merits or demerits, but have heard a lot of buzz around it recently. As you said, people tend to hype everything; some stay and some go. It might be the next big thing in programming, or it might be hipster tech. Or, like most things, it might have some good applications, but not be applicable to everything. That's basically my argument for OOP.
Encapsulation is "if you have a state, you should encapsulate it". It does not ask you to have a state (even less a mutable one). I quite often use object to represent a logical piece of code, without any attributes.
> I'm not sure functional programming has lost yet. I haven't worked with it personally, and so can't speak to its merits or demerits, but have heard a lot of buzz around it recently.
As much as i really enjoy FP, I don't think it has more than 1% of the market share of software engineering. And I've been hearing the "heard a lot of buzz around it recently" for more than 10 years.
That seems to be the crux of it - I remember reading a post by Paul Graham where he said something similar about object oriented programming. His core thesis seemed to be that functional programming does a better job of organizing things than object-oriented programming does, and once you have the core of functional programming in place (closures, first-class functions, whatever the hell a "monad" is), you don't need object orientation any more. I've never gotten deep enough into pure functional programming to really see things this way, but I've gotten deep enough to at least understand why these pure FP guys might think that.
And the world is not so neatly divided between things that just are (data structures) and thing that do things (functions). Take a date for example. The fact that it is a Wednesday is a "just is" sort of thing but is typically implemented as a function.
FWIW I'm struggling to come up with a good example of where the line between data and functions is clearcut, except perhaps when the data describes a function: an SQL string, some code that'll get eval'ed, etc.
You need some way to place in on that axis, though. Commonly we use Day, Month, and Year to do so. But we could also define a date as the seventh Wednesday in 2019. Or as an integer relative to Jan 1 1970.
I still think OO provides a pretty easy mental framework for programming. You can get good results. Bit of discipline without going crazy and it works really effectively. Despite its shortcomings.
It seems to me that part of the problem is that OO doesn't force you to have discipline and/or without constant vigilance (which product owners are never willing to schedule for) the system inevitably gets out of control over time.
On the other hand, it seems to me that the core principles of functional programming (immutability, functional purity, construction via composition, and declarative programming) serve as a check that prevents things from getting out of control.
That being said, I think it's worth considering that all of the "core principles" of FP that I mentioned could be incorporated into OO. It also seems like FP can be more prone to out of control syntax (e.g. unwise use of point free style).
The real issue: discipline has to be learned, often by experience, it isn’t forced. If you force it, then no one knows why things are they way they are.
It's more accurate to say "if you don't teach why' then no one knows why". Forcing or not has nothing to do with it.
I would argue "forcing" is strictly better, since learning discipline requires experience in doing things in every other wrong way. While that's great for learning on your own, I wouldn't want developers to "learn discipline" like this in production.
If I could go back in time and unilaterally make one change to Java, it would be to make `final` default. But if you just get in the habit of using it (the IDE helps), non-final variables look broken. And once objects have all-final fields, immutability just starts spreading upward in your code.
final List x = new ArrayList()
stops x being reassigned but does nothing to stop the downstream code from mutating the contents of the list.Imperative programming languages (like C and JavaScript) don't generally impose any discipline on the users.
I think this view disrespects history a little. C was one of the earliest languages to be conceived upon a foundation of structured programming principles, i.e. block structure, sequence/selection/repetition, subroutining. (Okay the language still has goto, hopefully we can agree to not make a big deal out of that.) The kind of discipline proposed by structured programming was far from universally well-received at the time, and I think it's fair to say that it lead to huge improvements in the quality of codebases everywhere, and is one of the great successes of programming language thinking of the '70s.
C is also statically typed. It's obviously easy to blow all sorts of holes in C's type system, but if you'd go so far as to say that C's types impose no discipline at all, I'd ask you to try teaching C to a room full of compsci students who have been raised on something like Python.
When you want a value from a module/object/function/whatever, whether or not it's fetched from a location in memory is an implementation detail. Java and co provide a short syntax for exposing that implementation detail. Python doesn't: o.x does not necessarily mean accessing an x slot, and you aren't locking yourself into any implementation by exposing that interface as the way to get that value. It's more complicated than Java or whatever, here, but it hides that complexity behind a nice syntax that encourages you to do the right thing.
Some languages provide short syntax for something you shouldn't do and make you write things by hand that could be easily generated in the common case. Reducing coupling is still a good idea.
That is the important thing sometimes. At other times the important thing is to provide a flexible, fluent public interface that can be used in ways you didn't intend.
It really depends on what you're building and what properties of a codebase are most valuable to you. Encapsulation always comes at a cost. The current swing back towards strong typing and "bondage and discipline" languages tends to forget this in favour of it's benefits.
That scares me. How do you maintain and extend software used in ways you didn't intend?
Quality assurance should be challenging.
getArea() {
return this.width * this.height;
}
getIcon() {
// if icon hasn't been loaded, load it
return this.icon;
}Most people don't have a problem with getters and setters, they have a problem with writing pure boilerplate by hand. Languages like Python and Lisp save you from the boilerplate and don't provide a nicer syntax for the implementation-exposing way, so people don't generally complain about getters and setters in those languages, only in Java and C++ and things.
Using an array without having to (manually) calculate the size of the objects contained within is like the major triumph of OO. This is a getter that you almost certainly use constantly.
Please try to consider your statements and potential counter factuals before spraying nonsense into the void
C# also 'fixes' that. o.x could be a slot or it could be a getter/setter.
My guess is there's a correlation between overengineering and career success, which drives it. Simple, 'KISS' style code is the easiest to work with, but usually involves ditching less essential libraries and sticking more to standards, which looks crap on your resume. Most interviewers are more interested in whether you can piece together whatever stack they're using rather than whether you can implement a tough bit of logic and leave great documentation for it; so from a career perspective there's zero reason for me to go for a (relatively) simple 100 line solution to a tough problem when I can instead go for a library that solves 100 different use cases and has 10k lines of documentation that future devs have to wade through. The former might be the 'best' solution for maintainability but the latter will make me appear to be a better engineer, especially to non-technical people, of which there are far too many of on the average team.
Recent writings by Joe Armstrong are also resonate with me the same way.
1. By declaring a getter without a setter, you could make a field read-only.
2. A setter could trigger other side effects. Specifically, the JavaBeans spec allowed for an arbitrary number of listeners to register callbacks that trigger whenever a value gets changed.
Of course, nobody actually understood or correctly implemented all this, and it all got cargo culted to hell.
A big problem is cargo culting without reading the CS references.
Ruby is far from being my favorite language, but I like how Structs "solve" the getter/setter problem in it:
my_struct = Struct.new(:field_one, :field_two)
It doesn't clutter your code with multiple lines of boilerplate, and it returns a regular class for you to use, not breaking the "everything is a class" constraint.With respect to your setters and getters question, particularly in regards to Python... The @Property feature in Python is just a specific implementation of the setters/getters OO design principle. I can easily be convinced typing foo.x is better than foo.getX(), but I have a hard time having a strong emotional reaction to one vs the other if the language allows them to have the same benefits.
It seems to be what we do, I’d say fp is in the same place. My CS program was heavily built around the ML family of languages, specifically Standard ML, with the algebraic types, functions, pattern matching (on your types,) etc. it seems like that “functional programming” is a radically different thing than what people do in js or erlang and call it that. It all comes around, I guess, static types were pretty gauche 10-15 years back and now how many folks are using typescript to make their js better?
Or you get values from your objects. You need getters for this, but you can guarantee immutability and apply functional programming principles to your code.
You can't have your cake and eat it too. At the end of the day, you need values.
Functional Programming (and Logic Programming) are better than other paradigms because, unlike Java (or C++, or C#...) there is an emphasis on correctness, and the people working on FP compilers (like Haskell and Idris) are utilizing mathematics to do this.
No idea on your opinion on mathematics, but to me Math/Logic reign supreme; the more mathematically-bound your program is, the less likely it is to do something you don't want later.
Compare this to Java. It's 2019, and we're still doing `if (x==null) return null` all over the place (I'm aware that the option type exists but that doesn't really help when it's not enforced and none of my coworkers use it). How about having to create six different files for something that I could have written in 20 lines in Haskell? Or how about the fact that the type system exists to help with optimizations, and due to a lack of support for structured typing, it can only be useful for that.
I realize that I'm picking on Java, but Java is the biggest target when it comes with OOP as the industry understands it. I personally cannot stand having to create fifty files do to something like a database wrapper, and in Java that's effectively the only way to program.
I had this experience once in a Rails shop.
A simple database table mapped to a CRUD API endpoint would take from five to ten files. That amounted to about 500 lines, plus a lot of tests for each class.
I never really understood why programming became so verbose. In an ideal world I'd have a declarative API that mapped the table to the API for me automatically. In a realistic timeline I'd just use the traditional Rails approach and be happy. But the people working there preferred to use complicated patterns and a lot of boilerplate before they were needed, even though the project was perpetually late and riddled with bugs. I wish we could give a chance to simpler ways of solving problems.
This is a bit of shameless self-promotion, but I've actually written a framework that's MVC-ish that lets you create really declarative APIs. The first version is written in NodeJS that I actually deployed in production [1], and I have an Erlang port that's semi-complete that I've recently started hacking on again [2], with the whole crux of it that you should be able to simply declare the composition of your actions.
[1] https://gitlab.com/tombert/frameworkeyPromiseEdition [2] https://gitlab.com/tombert/Frameworkey-Erlang
Maybe we learn something each time the pendulum swings but as someone knee deep in C# at the moment, the quality of the APIs I have to deal with are far below those I was used to in Python (at least in terms of elegance and usability).
I'm not sure whether these flaws are inherent or whether it's possible to have one's cake and eat it.
This sounds like the root of your problem. If you want to do FP, and none of your coworkers want to do FP, then your problem isn't really the language.
If you're in a Java shop, maybe start by evangelizing FP rather than a totally different language/platform? It's possible to do FP in Java, and (IMO) it ends up pretty reasonable. But it's not the default habit for J Random Java Programmer, so they need training.
I work for a brand-name big company (I won't mention it here but I'll tell you if you email me) that hires incredibly talented engineers that are a lot smarter than me. The codebase I work on is around ~20 million lines of Java, and I've seen stuff in there that is so incredibly gross that a compsci professor would write "see me after class" if you submitted it.
Example: I once saw a piece of code doing this:
do {
// doing stuff
} while (false)
It took me about 10 minutes of digging into the code to realize that the person who wrote this was doing this so that they could add a `break` in there as sort of a makeshift `goto` so they could early-exit and skip all the rest of the stuff in the block. Needless to say, I was horrified.Why is it that incredibly talented engineers are writing awful code like that? It's certainly not incompetence; what almost certainly happened was that there was some kind of time-crunch, and the dev (understandably) felt the need to cheat. This is a direct consequence of the compiler allowing a bad design.
You'll notice that it's virtually unheard of to use any FP languages in critical software. Instead they use languages that lend themselves well to code reviews, static and dynamic analysis, model-based design and proofs, etc. Like C, Ada and some domain-specific stuff.
The kind of "correctness in the small" offered by Haskel through its type system can be obtain also in languages like C++, Swift and others. With the additional benefit of massive market share, teaching resources, mature tooling and so on.
Erlang powers around 40% of the world's phone networks; and if it's not mission-critical I'm not entirely sure what is.
For that matter, Whatsapp is also written in Erlang and Jane Street does trading applications in OCaml. Without making a judgement on whether or not they should, both Whatsapp and Jane Street create very large apps and have created successful businesses with FP.
> Instead they use languages that lend themselves well to code reviews, static and dynamic analysis, model-based design and proofs, etc
I can't tell if you're being serious; are you suggesting that Functional Programming doesn't lend itself to proofs? Really? Have you ever heard of Coq or Idris or Agda? They literally have modes to prove the correctness of your code.
What about functional programming doesn't lend itself to code reviews? I did F# for a living for two years and we had regular code reviews. I also used the .NET performance profiling tools which worked fine for F#.
> The kind of "correctness in the small" offered by Haskel through its type system can be obtain also in languages like C++, Swift and others.
Uh, no. Sorry, that's just flatly wrong.
Yes, static analysis tools are awesome, but you will never get the same level of compile-time safety from C++ that you will from Haskell or Rust or any number of functional languages. The type systems offer very little information, making it impossible for the compiler to shield anything.
Took me a few reads but it's better stated "six different classes". At first I was confused about why you rely on `java.io.File` for business logic.
So, if I'm stuck on JVM, what's my FP alternative that compiles and runs comparatively? Clojure? Scala?
Because it is principled but also very pragmatic.
If I'm stuck in JVM-land, Clojure followed by straight, modern Java would be my choices.
That said, and I addressed this specifically, when I say "OOP" in the software world, people typically think of Java, C++, or C#, and those are what I'm addressing specifically.
I suppose in the most technical sense of the word, you could argue that Erlang is OOP at some level, and Erlang is awesome, so if we want to play with definitions then sure, I'll concede that OOP is good, but until the industry as a whole agrees on these terms, and doesn't treat OOP as a synonym for "Java/C++/C#", I'm still going to say that I hate OOP.
((if (null? x)
;.....
))It's easy to hate on OO because of something along the lines of it not being a neat mathematical formalism, which can facilely be argued as strictly a deficiency: if you don't look too closely, it certainly appears as only a deficiency.
I think a deeper look inevitably runs into two things:
(1) certain domains are more easily approached through spare mathematical formalisms than others. E.g. if the domain you're modeling is already most easily thought about in terms of compositions of mathematical transformations, you should probably model it functionally.
(2) Finding a declarative characterization of the results you'd like, or a neat chain of functional compositions which produce it, typically takes more work up front. (For many projects, the initial work up front is worth it—but for lots and lots of others, it's essentially over engineering.)
OO is often not 'ideal,' but frequently, solidly pragmatic.
As a paradigm, the aesthetic behind it reminds me of TypeScript's designers intentionally foregoing soundness of the type system.
This model works where it actually represents the real world: Mostly, in distributed systems.
In other areas, it just leads to overengineered piecemeal crap that is incredibly hard to understand. Where you can get control over what happens, you absolutely should get it. Don't act like your program was a thousand little independent components that have their own mind and lifes. Because it isn't like that, and if it was, there was no way you could actually get in control of these to make them produce a very specific outcome.
So the only reason why many OOP programs sort-of work is because programmers never actually respect the abstractions that they set up by defining so many classes and methods. To get the program to work, one needs to know very precisely what each class does in each cases. In the end OOP is just a terrible farce since there is no rhyme and reason for all these classes. It's needless bureaucracy, and prevents us from structuring programs in a more efficient and maintainable way.
Without OOP, we have the ugly organization of writing N functions that each dispatch M cases of code by pattern matching or switching on a numeric type field or whatever.
OOP lets us break these pieces of logic into separate methods. And then in the physical organizationof the program, we can group those methods by type. For any given type, we have N methods: each one implements one of those N functions just for that type.
This is a better organization because if we change some aspect of a type, all the changes are done in one place: the implementation file or section of file for that type. Or if a new type is added, we just add N methods in a new file or section; we don't have to change the code of numerous functions to introduce new cases into them.
Those who write articles opposing OOP never seem to constructively propose an attractive alternative for this situation.
It is this attractive program organization which swayed developers toward OOP, or even full blown OOP evangelism. It's not because it was hyped. OOP has concrete benefits that are readily demonstrable and applicable.
OOP is what allows your operating system to support different kinds of file systems, network adapters, network protocols, I/O devices and so on.
It's unimaginable that the read() system call in your kernel would contain a giant switch() on device type leading to device-specific code, which has to be maintained each time a new driver is added.
With OOP I can add a new datatype easily, but when I want to extend the behavior of that type I now need to go to M different places. With a functional style I only need to do one. You're open on types but closed over behaviors. Functional styles are the opposite.
In some sense, I would even go as far as saying the idealized 'UNIX philosophy' is a degenerate example of this. We have a very limited set of types (the file) and a bunch of independently implemented behaviors. Imagine implementing sed or grep on a per-file (or per filesystem) basis.
These both get really interesting when you consider libraries/user extensibility, since unrelated actors could now add either new types or new methods. Most languages just punt on this by banning one or the other.
A pattern matching style would allow me to add a new sql() system call to query into the filesystem. Look at how much trouble there is adding new features to CSS, TCP, Java, etc trying to coordinate among so many different actors.
Or consider the case of a programming language AST. I can make a pretty printer, an interpreter, an optimizer, a type checker, a distributed program runner. But trying to do that with an OOP style is much harder for a large AST.
At the end of the day, we have NxM (type, behavior) pairs and there are pros/cons to each way of slicing them.
Very true. It's a practical solution to a complex problem. However, when systems get complex, it becomes very hard to find the right object / type to bottle up logic. Perhaps, then, a mix of OO and functional is the solution.
The problem is that OOP is a slate of something like 18 characteristics and no language ever picks the same choices.
That having been said, the big problem with (especially early) OOP is that "Is"/"IsA" (aka structural inheritance) is the primary abstraction. Unfortunately, "Is"/"IsA" is a particularly lousy choice--practically anything ("Contains" or "Accesses" or ...) is better.
Most of the modern languages designed in the past 10-20 years reflect this--"Traits"/"Interfaces" seems to be what everybody has settled around.
In the programming work you do regularly, do you think there is a way you could structure your code which would allow you to do 60% less work, without sacrificing readability? If there is, would you know? The answer was clearly yes in the web world when jquery was king. Or cgi-bin on Apache. Is it still true today? Can we do better still?
If there is, it would probably demand a critical look at the way we approach our problems, and how we structure logic and data. The value in articles like this is to point at that question. For what it’s worth, I agree with Joe Armstrong and others who have been heavily critical of OO. Software which goes all-in on OO’s ideas of inheritance and encapsulation seems to consistently spend 2-3x as many lines of code to get anything done, compared with other approaches. (Looking at you, enterprise java.)
You’re right - the problem should proceed the solution. But our tools are the medium through which we think of that solution. They matter deeply.
Not all OO works that way. In retrospect, inheritance was probably a mistake. And as far as I can tell, modern “OO-lite” coding styles focussing on composition over inheritance work pretty well. Alan Kay: “When I invented object oriented programming, C++ was not what I had in mind.”
In other comments here I explain why "object-oriented" was a too quick and bad choice for what I thought I was doing ...
My follow up OOP languages until 2000 were C++ alongside OWL, VCL, MFC. Clipper 5.x, Eiffel, Sather, Modula-3, Oberon variants, Smalltalk, CLOS, SWI Prolog, Delphi and naturally Java.
In 1999 I got a signed copy from the ECOOP 1999 proceedings, full of alternative OOP approaches.
We should strive to actually provide proper bases to CS students, instead of market fads.
Understanding the objections to various programming paradigms can help improve how you use them, by having an awareness of what others consider potential minefields. (And who knows, maybe the arguments will change your mind. You shouldn't be so quick to prejudge the material.)
Most of us work in teams. If people I work with believe something that's not true, then it directly effects my work. If the majority of the profession believes something, it significantly effects my entire career. I don't actually hate OOP, although I have criticisms, but the attitude of "if you don't like it go somewhere else" is missing the point. Criticism isn't meant to be mean or nasty, it's meant to point out bad thinking for the benefit of us all. What bothers ME is the positivity police on hacker news that thinks that "if you don't have anything nice to say don't say anything at all" applies to all of life.
How many of us actually get a choice in this...?
I mean, most devs don't really know anything else, just accept it and try to get by...
>>Clearly OOP works for a lot of people.
Something can work for a lot of people, and still suck.
>> If it doesn't work for you, don't use it.
Language and tool choices in our industry are made by a tiny minority. In fact, sometimes the people making those decisions are not even developers themselves! From that perspective alone, articles like this one are valuable.
Aside from that though, the question isn't whether OOP "works" or not. Rather, it is when it works, and for how long, until you run into a myriad of problems, such as leaky abstractions or inheritance hell. These are worth discussing. If you disagree, you can move on. No need to voice your misgivings.
Binding data and functions together beats operating on global data visible to everything. One of the big wins of OOP is less exposed global data.
A big problem with OOP in C++ was that it wasn't opaque enough. The headers needed to use an object contain info only useful to the object's private functions. This creates recompile hell.
Multiple inheritance is seldom worth the headaches.
Overriding a member function needs declaration support. The usual cases are 1) the parent function needs to be called first, 2) the parent function needs to be called last, and 3) the parent function needs to not be called at all. Which case is to be used is properly a property of the parent, but in most OOP languages, it's expressed as code in the child.
The use cases for generics are rather limited. Collections, yes. Beyond that, it's usually someone getting cute. That way leads to the "metaprogramming" mess. Go has a few built in parameterized types - maps, arrays, and channels. Go2 may have generics, but the designers are struggling with what to add that doesn't take them down the rabbit hole.
Objects are probably more useful than some of the things invented to replace objects, like "traits".
... These things are unrelated. Just write functions that don't operate on global exposed data? You know, like, function arguments.
That's a good idea! Although... I wonder if you would get tired of passing around the same piece of data between such functions all the time? Here's a crazy idea: what if we pass the data implicitly to such functions? Like, we could pretend that the data was passed to the function as a hidden argument, and we could call this argument "self" or "this"! :O
“Passing state as parameters” is what solved the “everything operating on global state” problem. Binding functions and state permitted polymorphism/abstraction.
> Multiple inheritance is seldom worth the headaches.
Single inheritance is never worth the headaches. :)
> Objects are probably more useful than some of the things invented to replace objects, like "traits".
Traits don’t replace objects; they are interfaces with static dispatch semantics.
When you code C and write something as simple as
fwrite( "Hello world", 1, 11, stream );
that line of code changes state of the file stream object in CRT, state of file caches in OS, state of B-tree nodes in file system driver, state of disk firmware, state of NAND flash chips…It’s not just OS and drivers. Any sufficiently complex software is built by layering abstractions on top of each other.
Many problems that need to be solved have very complex state, often related to IO or GUI. You don’t have other choice but to use some form of OOP and hide lower-level implementation details behind abstractions, otherwise you won’t be able to get anything done due to overwhelming amount of state to reason about.
Do you think it was counterproductive to hide OS-specific file handles, CRT level of caching, and many other things CRT does behind these opaque FILE* object pointers?
Such hiding allowed to have very similar and quite easy to use <stdio.h> across all platforms.
The implementation of these fopen and fwrite functions relies on kernel calls like open/write on Linux and CreateFile/WriteFile on Windows. Do you think it was counterproductive to hide all the lower-level stuff like OS caches, inodes (linux) / NTFS (windows), and the rest of them, behind these OS kernel calls?
If you’ll decide to unhide them, your OS won’t have any security at all, the OS file system cache is security sensitive. Modern OSes implement strict access control while they hide that piece of state, every file has permissions, every process accessing them has user identity, and the OS checks these things when process access files or file system cache.
To answer that question for parent, no, he obviously doesn't think so. And yes, streams are basically OOP (they are implemented using "method dispatch"). Streams are one of the few successful abstractions out there.
And when I say "successful" I mostly mean "necessary". Because they're leaky. Consider fseek(3), ftell(3), fileno(3), fflush(3), fclose(3), fsync(2), posix_fadvise(2), isatty(2), fcntl(2), ioctl(2) and many more. All operations that are not supported for all streams, but necessary for some (like file system files, network connections, terminals devices...). Essentially when you use any of these you concede to break the abstraction.
It's incredibly hard to handle these streams correctly, and the vast majority of programs are faulty in that they don't handle most error conditions correctly. In some cases, it isn't even possible to handle errors through the API! See close(2)
So again, not saying we shouldn't have a stream abstraction. But abstractions come at a huge price, and therefore it's incredibly misguided to make every program a layering of thousands of pseudo-isolated objects that bring their own bug-prone abstractions and idiosyncrasies.
I don’t think they’re few. I think all modern software uses OOP-based abstractions heavily.
For example, for GUIs, it’s visual trees everywhere. HTML DOM is the most widely used one now, but pretty much every GUI framework have something conceptually similar, even 30 years old WinAPI is built on very OOP-like concepts, you have HWND handles but the state is completely hidden and can only be accessed by calling functions.
> Because they're leaky.
No abstraction is 100% waterproof, but some are useful enough for many practical applications, despite they leak occasionally.
These particular file I/O API is less leaky on Windows, but they still leak, and there’re many weird low-level APIs to bypass these abstractions: DeviceIoControl, defragmentation API, backup API, and many others.
> abstractions come at a huge price
I’m not convinced the price is > 0.
We can always skip any abstraction we don’t like and use the next lower-level one. We don’t often do that because developing code based on these lower-level things is often more expensive than using the abstractions, despite they leak.
That’s why people use Electron & JavaScript, Java & .NET, game engines. They all made from large count of the layers you’re talking about, and when they leak that’s indeed can be very expensive to diagnose and fix/workaround. However, dropping them and developing something from lower levels is usually way more expensive.
It’s the same story with all high-level tools. They’re easier to use and have lower barrier to entry, used by less skilled programmers who fail to deliver good products.
For example, Unity3D has very low barrier and there’re many low-quality games built with that. But if developers know what they’re doing it is possible to build great games, e.g. cities skyline.
void* ptr = malloc( 32 );
No OOP is involved here, yet the malloc function operates on a critically important piece of mutable global state. The state is quite complex due to numerous reasons: performance, fragmentation, multithreading & deadlocks, alignment… Just look at ptmalloc (linux) or jemalloc (BSD). I think it’s undocumented on Windows but I’m sure it’s at least as complex, search for “low fragmentation heap” which is the default one since Vista.I don’t think hiding that state in layers of abstractions is counterproductive, I think it was good, due to the following reasons.
1. In 99% cases makes it easier to write code. Heap state is very complex in practice, and just like file IO, it spawns across user mode state of your process and OS kernel state. Hiding the state all together allows programmers to reason in terms of very simple APIs, it’s just 2 functions malloc/free with very clear purpose each.
2. Hiding that state makes bugs in your code less likely to corrupt it. If you instead have something like void* malloc( size_t bytesCount, struct HeapState *processHeap ) it becomes easier to accidentally corrupt that state.
3. Hiding that state decouples consuming code from implementation. On Linux, that state is hidden deep inside libc library. You can replace the complete implementation of that state, or even eliminate it if you want so, the consuming code will run fine as long as malloc/free API stays stable. Quite often, code that uses explicit state becomes dependent on the particular implementation of that state. Much less likely to happen when the state is hidden behind some API, OOP or not doesn’t matter.
For malloc, address space fragmentation is a common issue for 32-bit processes. Multithreading issues are common, when 2 threads access same cache line, it’s even slower than accessing main RAM. Randomized nature of the allocator causes performance problems.
Every time people bother writing custom allocators, they do that because malloc/free abstraction was not good enough for them, i.e. has leaked.
All abstractions leak, OOP or not.
I wouldn't consider this an abstraction at all, and it doesn't leak nearly as much as a "streams" abstraction - if at all.
You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.
https://www.johndcook.com/blog/2011/07/19/you-wanted-banana/
...& if you don't want the stateful gorilla?
Mostly, I've yet to see something better that has the maturity, libraries, depth of talent (!), etc. that doesn't have problems of its own. It's certainly not perfect, but there are reasons it has remained so popular in many fields.
I swear C++ gets a bad rap because most people have only seen a gross mixture of C++98 and C in their codebases
A fully modern C++11 codebase is a beautiful thing
That's not how having a global utility function works in any OO language I can think of.
Can you point to an example of such? It would be nice as someone who’s been away from C++ for a long time.
This post is not different.
I mean, sure, Java taught bad OOP to millions of developers in the 90s, early 00s, and even today. Sure, the legacy of bad OOP lives on today.
That doesn't say anything about the principles of OOP when applied properly, when done in languages that avoid the pitfalls of bad OOP, and when the developer avoids the very patterns that tend to fall into criticism.
(incoming FP advocate to call my statement a strawman argument in 3, 2, 1…)
I think it's the particular combination of only having nominal typing, only allowing the oo-flavor of data abstraction, and encouraging programming with state and using inheritance for code reuse that leads to the object-spaghetti nonsense in heavily-oo Java/C++ codebases.
The problem is not that OO languages have these features; the problem is that they lack other features common to functional languages.
I don't understand the above:
a. functions do things,
b. objects don't do things,
therefore, we shouldn't combine them together?
Why? I'm not convinced? what's the logic? doesn't seem to be a strong argument to me.
factories make things.
warehouses don't make things.
so we should build them together?
So... that's not actually how it works though, right?
No it fukin doesn't. OO is a paradigm, useful at times, nothing more.
That's what modern programming is all about - you pick the right tool for the right task.
OP's attempt to force OO onto an abstract concept like time is so far off the mark calling it a strawman wouldn't even be the right word.
An object should also have completely private members and no concept of inheritance. This greatly simplifies thinking about and designing objects. There should be no distinction between a regular and special method like a constructor or destructor. They should essentially be simple constructs with no magic that force interacts with a particular kind of data through functions that maintain the invariants. That's it. That aligns with the message passing origin of OO and Erlang in that there is no "breaking the rules" to get to an objects state.
This is basically how they work in Rust and Go. The key in both of these languages is the use of traits and interfaces to obtain special behavior and polymorphism. Neither of them forces a composite type to be completely public or private though, and Go limits visibility to the package level.
I used to really hate OO, but I started to think about what the parts I really disliked about it, and saw that in Erlang, Haskell, etc there is always a need for an "object" whether it be a GenServer or a Monad. Java and C++ just took the concept too far, and took an absolutist approach that most features need to be part of the OO machinery, although Java is by far the worst. Objects have their place, but they should be just as common as any of other type, and should not be used to think of program structure in most cases.
Here are some of the things I'd like to see in programming languages:
(1) Some semantics that admit some useful static analysis, that is, tell me some useful things about my code, e.g., for a variable, where does it get used and where might it get changed?
(2) Offer me some useful code transformation properties. For this, a start is the relatively powerful scope of names constructs in PL/I: There can just drop into the source code another function and know that in the more common and important respects that function being there won't hurt anything else already there. So, if I have some function I like for something or other, then I can just drop it in.
There's a lot more: The main point is just to try, so that given 100,000 lines of typing of code, some tools can tell me where the lines of spaghetti and the meatballs are!!!!
So, that code is a system and treat it as such, i.e., instrument the thing and tell me what is going on, both statically before it runs and as it runs.
Likewise I've also had to deal with a 'big ball of structured programming spaghetti'.
My take on the difference in these two types of systems is they are generally the result of too much coding with too little design.
Personally, I think the OOP approach does have a tendency to turn into that spaghetti ball more easily, only because it needs more up front design.
Most of places I've worked who adopted an OO approach have tended to focus predominantly on the OOP with little or no attention to the OOD and that tends to be a recipe for disaster.
Composition or inheritance or template or concrete? It depends. Extract methods? It depends. Design patterns? It depends.
Although to master good OO design - knowing what and when to do something - is surely an art, it doesn't handle dynamic business model very well.
If I just express the business, I cannot handle requirement change. If I want to deal with some future change, I have to do a lot of non-business oriented design, making the code less expressive.
And most of the OO language don't have algebra data types, which makes expressing business model more awkward.
It's about creating discrete APIs so that a bunch of different programmers can work on different aspects or sections of code.
It's an organizing principle for discrete elements that encapsulates internal state. You don't need to know your datastore is SQL or Redis, or Redis caching SQL data, or marshalled JSON, you just call Users.getUser(id), and get a User Object with a defined API.
OO is in many ways just microservices for a single codebase.
I think people ignore the importance of OO in terms of unit and integration testing.
The only thing that strikes me as specific to OO is implementation inheritance, and that has been an unmitigated disaster.
I'm saying it is a way, and a sometimes useful one. Certainly when I'm thinking about a SQL schema, I'm often thinking about it using OO design principles, even though SQL is functional.
Functions and data are in fact tied together. Almost any thing in mathematics is described in terms of some collection of properties which are elements of sets, and operations. The operations are part of the representation.
A Universal Turing machine is the tape, the symbols, the rules of how the head moves and reads and writes symbols, all together.
I don't have anything against it, and wielded with skill I see its benefit. But after 30 years of programming I think you can achieve many of those benefits more simply without it.
When he talks about state for example, I assume he means mutable state. Everyone knows this is best avoided if possible. The vast majority of OO languages provide mechanisms to avoid mutability e.g. data classes or keywords to make references constant.
He also rails against private state specifically. I assume again that he means private mutable state. This is generally a bad idea and is accepted pretty uncontroversially as a bad idea. The principle of data hiding in general however is definitely not a bad idea. Being able to enforce scoping rules on classes/functions/members is not meant to facilitate creating 'black box' entities whose internal mutations are difficult to reason about, but is instead meant to provide guarantees about dependencies between different parts of a system, i.e. Separation of Concerns.
Regarding 'everything is an object', in many cases this is perfectly acceptable as it guarantees some sort of basic interface that all entities conform to e.g. being able to ask an entity to provide a hash code, or to carry out a comparison check with some other entity. Some modern languages like Kotlin even eschew the concept of primitives altogether and just make everything an object.
Finally, regarding his point about OO languages having data type definitions all over the place, I must be misunderstanding something here... Why on Earth would you want all your data type definitions in one place?
Anyway, just wanted to give my $0.02 lest anyone get the impression that these are 'knockout punches' against any and all OO languages.
Well, clearly not everyone knows it.
My biggest complaint about languages that incorporate functional features is that immutability is most useful when it’s the 99% use case.
When it’s a special tag that may or may not be used within the library/code base you’re using, you don’t get the cognitive advantage of feeling confident you can judge the output of a function by reasoning about its input.
My brain is small. I need all the crutches I can get, and referential transparency is a huge advantage.
So, functional programming?
> guarantees some sort of basic interface that all entities conform to e.g. being able to ask an entity to provide a hash code, or to carry out a comparison check with some other entity.
Isn't that terrible? You want to provide a generic interface to calculate a hash or perform comparison: Now the thing has to be an object! If it's a primitive type, you better wrap it in an object now. If it's an existing class, you have to extend it. It becomes an object of a different class.
Or, instead, you could just have type classes to let the same apply to anything whatsoever, as long as it's a type, without the need for it to be an object. Heck, in some cases, the type could be a function instead.
Here's a program that has some mutable state. Do you want that mutable state to be private or public? Do you want anybody to be able to change it, or do you want all changes to have to go through public methods?
Given that there are programs that necessarily have mutable state, why in the world would you not want that state to be private?
What do you do with values that can't be meaningfully hashed/compared? Java is the only place I've come across this odd idea that everything is hashable, and I've never thought of it as a positive thing.
Data structures shall be bound to functions. It increases modularity. That does not mean common code shall not be refactored and be standalone.
Data types are instantiated to objects. When in Erlang a time variable is declared, that variable is an object. OOP has data types too, called classes, and having everything to be an object does not mean it has to be the same data type.
In some languages classes can be in the same file, so point #3 is actually about specific implementations.
Inside point #3 he also mentions inheritance. Not all OOP languages require inheritance from a specific class.
I also disagree with his 4th point about hidden state: hidden state enhances modularization tremendously. I don't need to see how something is implemented, treating it as a black box increases my chances of using it correctly through a predefined interface. If the interface is lacking or is not defined correctly, that's an entirely different problem and it's not due to hidden state.
Objection 2: I agree not everything should be an object. However where do you draw the line? To me a timestamp is a perfectly reasonable object. I can see the pros and cons of "3" being an object as well.
Objection 3: I like things to be organized. What benfit do I get by jumbling everything together in one spot?
Objection 4: Yes they do, pretending they don't doesn't help. Even in Haskell, files and sockets are represented as an opaque handle to hidden state. It very much looks like a OO interface in a non OO language. Given the three data structures in objection 1, hiding the state allows for a smaller surface area to prove that the data structures are correctly implemented. Allowing random access to their state does not improve them.
This is my biggest issue with the article. Blurring the boundary between program and data is one of the core principles at the heart of computer science, and was crucial to the works of Turing, Church, et al, that birthed computer science as a field.
In fact, the OOP question isn't as much about science and technology as it is a social question concerning the best way to organize logic and information as a profession. What's the best way to make sure programmers don't screw things up? Is it to make sure the most incompetent developer can be somewhat productive with simple concepts, or are those simplified concepts inherently flawed? Or is it somewhere in the middle?
Around the late 1980's OOP was hyped as a way to do domain modelling. That is, modelling the nouns and verbs of the subject at hand (employees, invoices, etc.) While some still defend it for domain modeling, most have found it a poor fit for any non-trivial model.
But it did turn out a handy way to present and manage APIs to "external" libraries or services. This is largely how it's used today.
The lessons:
1. Use the right tool for the job, and no one tool is right for everything.
2. Test an idea in production for a while before drawing conclusions about what its good at and what its not.
Microservices is currently making similar mistakes, I would note. Those who don't learn history are bound to repeat it.
OOP is not a programming feature, it is a software engineering feature. It allows for cleaner APIs and higher levels of abstractions. IMO the core feature of OOP is less the ability to bind functions with data but the ability to overload operators.
Sure, you can have functions that have the "this" pointer as the first argument and have exactly the same things. You can add a bunch of flags and have the core features of inheritance.
You can. Now what do you prefer to write, when finding the middle of a 3d segment?
mid = (a + b)/2
or
mid = scalar_division(vector_add(a,b),2)
?
If you allow operators to be overloard, is there any good reason to not place them close to your structure definition? Isn't it a good practice to force these functions to be defined if your structure is?
The core idea of OOP is that it extends the language by allowing to build more abstract concepts in top of lower level abstractions.
That's a core misunderstanding that I keep seeing with low-level programmers. They want to see the implementation details of everything and refuse to obscure some behaviors and trust the libs/compiler makers.
People who spend more time in higher level algorithms can't bother with all the implementation details. When I do DL, I want the ability to concatenate layers of different types (that inherit the same generic type), be able to check their output(), manipulate tensors of floats, assign them a scalar value or multiply them by some.
You can go a very long way without using any kind of OOP and staying at the implementation level. I am actually in awe of how much one can do that way. But OOP is a tool for teams to work together without knowing the details of each other's parts and build increasingly complex abstractions.
Really there’s an easy conclusion to this — allowing and preferring user defined operators or operator overloads is a hotly debated topic and one where there appears to be no right answer — about the only conclusion you can draw then, is that operator overload is not exclusive to OOP: https://softwareengineering.stackexchange.com/questions/1809...
I’d also suggest that with functional programs, there are limits to how much you should cram in to one program, and that a smart way to modularize your code would be to, for example, follow the Redux reducer pattern and build or compose larger state objects and operations from functions that operate on just parts of the state object—this way you isolate written changes in a similar way to private OOP variables, where it’s just not expected (or even “in scope”) to modify other parts of the global state. Additionally, you can as in OOP control access to state by encouraging the use of “selector functions” or not directly accessing state. You could make your functions take smaller typed structs of state, really, functional programming is at least as expressive as OOP programming. I’ll say that both allow you to make mistakes like mix concerns, or use lots of globals, perform magic with meta-programming or overloading, or not be expressive enough to create your own DSL in—OOP or Functional, these concerns are in my experience shared by basically any language.
Also outside of numerical work and some set stuff there's not really that many times you actually need to overload operators.. And you can easily extend languages without objects (Lisp)
When I use OO, my main motivation is also that it's easier to share code and debugging becomes much more predictable. (Aahh, it's in this encapsulated part of the code...)
Thank, Joe.
OO is not easy after all. You might be familiar with all the patterns which can give a sense that you know OO well, and you end up with pretty bad design.
Of course, it should be a string. However ‘File’ object is a better example that favors OOP. When you call ‘.read’ on a file object, you expect its content. It doesn’t matter if it’s a windows file, unix file, an IO string, or a web resource. When you call ‘.read’, it just work. Whereas in the functional world, you’ll have to 4 different ‘.read’ function calls with an if statement.
A good language for functional programming provides parametric polymorphism via traits or typeclasses:
pub trait Read {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
}
impl Read for std::fs::File { /* ... */ }
impl Read for String { /* ... */ }
Then you can write a generic function like this: pub fn read_kilobyte<R>(r: R) -> io::Result<Vec<u8>>
where R: std::io::Read {
let mut buf = vec![0u8; 1024]; // vector of 1024 0s
let nbytes = r.read(&mut buf[..])?;
buf.truncate(nbytes);
Ok(buf)
}
And call it like this: let kb1 = read_kilobyte(File::open(...)?)?;
let kb2 = read_kilobyte("foo bar baz quux".to_string())?;
No ifs in sight.I mean, I don't see anything horribly functional-language-specific about your trait example.
Traits are not functional-language specific except in that they are often used in FP to solve problems similar to those one might use inheritance to solve in OOP.
I fear I've slipped somewhat into OO habits by using Go interfaces.
> Whereas in the functional world, you’ll have to 4 different ‘.read’ function calls with an if statement.
You could do this but that's not idiomatic.
What you would preferably do is have a function that takes a read function as a parameter. This is essentially the same thing as what OO languages implicitly do.
For an example of generic functions you can take a look at map or fold/reduce. You'll see they don't need to use if statements in the way you described.
As soon as you start modeling real world situations (like bill being an employee but at the same time working a late shift as a contractor), then you're in the world of complex stuff that single inheritance, multiple inheritance, or anything less than insanely complex relationships requiring tens or hundreds of underlying entities to enact just can't handle.
IMO one reason for these fruitlesd ideological arguments -is OO good? Is it bad? - is that OO was originally sold using these real world examples. Which anyone who's worked on a real world system knows is BS.
Also I highly disagree with the assertion that "Data structure and functions should not be bound together". I would argue that if you are using any sufficiently complex or niche data structure that is not part of some standard library or a primitive, you absolutely do want to bind functions to the datastructure. It is much better to define a data structure with a set of logical human-readable functions for what you want to do with it than to keep it as some collection of primitives or standard lib datastructures you pass around everywhere. Let's say you are writing a spell checker. You really want to abstract away all the ugly details of the trie and any other helper datastructures. You also may want to create an interface for some "word library" that you can extend with different solutions to test e.g. performance (if you are trying to optimize for caching). Object oriented programming really simplifies things in a scenario like this.
* Domain = logic + data
* Application = logic + data
* Object = logic + data
Nothing wrong with using functions and data objects. But they're often still interdependent. A class just makes it official.I feel like I am not trendy enough to know what the latest development religions are.
I've always heard of Pascal as "the Java before Java" or atleast quite an OO language. And since I knew Java I never thought to even go and look at it. Did I miss something here?
Also simplicity is relative.
Most of us like OOP because we have learned it and invested time in it and most probably defined by it.
> Objection 1. Data structure and functions should not be bound together
In languages like OCaml and C, one defines types separately from functions. Sometimes, however, I find that a function is best associated with a particular type. In OCaml, that's one purpose of modules. (The convention is to call the main type "t.") However, this organization shouldn't be forced on you.
> Objection 2. Everything has to be an object.
This objection only applies to languages like Java that force OO on you. In OCaml or C++, I don't have to use OO, but I still can if I want to.
> Objection 3. In an OOPL data type definitions are spread out all over the place.
Armstrong complains about having to decide what to inherit from when making a Time object. IMO this is a strawman; in Java, classes inherit from Object by default and I don't see any inconvenience. I do agree that it can be annoying that Time has to be object-oriented, and this is where the benefit of OO being voluntary comes in.
> Objection 4. Objects have private state.
I either disagree with or don't understand this objection. Dr. Armstrong lists three ways to deal with state.
His third option is to use pure functional programming. I see both the beauty and practical value in making functions be functions in the math sense, that is to say, mappings from elements of the domain to elements of the codomain. Then, you get equational reasoning. I am sympathetic to this idea. Nevertheless, languages like Haskell aren't for everybody.
His second option is to control access to mutable variables with scope. You can implement a pure function in an imperative way, and that can be a form of encapsulation. This is a good approach, too.
Dr. Armstrong's first option, which he says is the worst, is the idea that objects maintain hidden state which people control via methods. To me, this isn't about state, but rather encapsulation and abstraction. The idea is that when one, say, inserts into a hash table, the person doesn't think about the implementation ("hash the key and walk down the corresponding bucket"), the person thinks of the abstract problem domain ("map this key to this value"). The hash table interface hides its internal state behind the methods. Even in a programming language like C, one writes functions that operate on, and mutate, structs to abstract things away.
Although the second option is good, I see it as orthogonal to the first option, not a superior alternative. If you are going to confine state to within a function, ultimately, don't you have to do mutations on the local variables, and therefore call some kind of method or impure function on them? However, perhaps I am misunderstanding this fourth point.
A lot of arguments, including this submission, pit OOP against FP and argue that FP is superior to OOP. Even though I use FP, I also think that OOP can be useful. They are not opposites and don't have to conflict.
OCaml is infamous for its object layer, yet I ended up using it for a project. I wanted to describe UI "widgets" such that I could compose arbitrary different types of widgets. Before deciding on objects, I had considered first-class modules or a record of functions, but ultimately decided that objects were the most readable choice.
In my OCaml program, I mainly viewed OOP as a way to abstract different types into a common idea. Widgets are all different, and they have different behaviors, but they all have a position in space, a length, and a width. One can also reposition them (mutation!). To me, the purpose of OO is to express ideas such as these. I note that when I used OOP here, I cared less about state and more about the abstraction of what a "widget" was, via dynamic dispatch. It is bad when the language forces OO on you, but it has valid uses.
Which brings be back to the original objection. I think this is true most of the time, except when it's not - which is your core Domain Model. The first 1/2 of the Blue Book[1] lays out straightforward means to arrange code, functions, data/state and related behaviours in a way which can be managed and maintained over time. This is pretty important as most folks who've spent any length of time maintaining vast applications will know that it's incredibly hard to reason about a first-class concept in an application without clear boundaries around said concept, it's structures and it's behaviour. Most of us are unlucky and find this scattered across the landscape. Few applications take the focus to "model" these concepts clearly.
Does this modelling have to be done with "Domain Model", or DDD, or something else that can be loosely coupled with OOD - probably not. But another developer absolutely has to be able to reason about said structures and behaviour. They have to be able to read it easily and grok it quickly. And having done that, they don't want to be surprised by some distant missing element, 20 calls or 1000 lines or 15 modules (repos, submodules, etc, etc) away! This is possibly the biggest time-sink and therefore "cost" of development. One could also take this further and postulate that about 1/2 of us are employed as a direct result of applications whose core concepts are so poorly designed or hard to reason about, that a massive volume of work (time?) is dedicated to unwinding the ambiguity that results.
I don't want to suggest that OOP or OOD/DDD/{other modelling process} would necessarily fix this, but the attempt to clarify and find a means to make modelling these critical concepts easier and less costly is admirable IMO.
It's ok if your infrastructure takes a different approach, or is "functional" or "dynamic" in nature. If your test suite uses completely different patterns and paradigms because the tooling makes that easy then - awesome! But if the core model/concepts of your application are hard to understand, reason about, and therefore maintain, then you're pretty fucked.
OO doesn't "suck". It's spirit is just largely lost and like many other things in life, it's been hijacked and mutilated into something many of us come to loathe because we've never seen it deliver on the promises. I guess we will be having this conversation again in another decade about something else that's hugely popular right now.
[1] https://www.amazon.com/Domain-Driven-Design-Tackling-Complex...
Rule of thumb?
Anything that can reasonably be done in FP should be done there, in 'library' like conditions (some people use the term 'FP core' - I think more as lib).
The point is to parse out as much of the problem into highly individual parts that can be built/tested on their own.
Think of parsers, pre-processors, utilities like lodash, node libraries.
These things tend to be truly stateless.
Then OO for the inherently stateful stuff.
I also think the discussion might be coloured by the type of dev: UI is fundamentally stateful. Very much so and there's no avoiding it. OO lends very well to abstractions such as 'button' and 'text field' etc.. Backend, maybe less so, depending on.
Excessive application of OO and FP design patterns, in addition to increasing complexity and error probability, reduce performance, without any benefit. Complex networks of relationships between objects in the OO system are also difficult to maintain.
I tend to construct systems with the simplest concepts and the most basic techniques, syntax, and functions. Used to implement my mind, The Pure Function Pipeline Data Flow is the simplest, stable, reliable and readable.. There is a great poet Bai Juyi in China. even illiteracy understands and appreciates his poetry. I hope that my code can be understood by the junior programmer even in the most complicated system.
For me, programming is the process of designing a data model that is simple and fluent in manipulation. More than 80% functions of my project is ->> threading macro code block, each step is simple, verifiable, replaceable, testable, pluggable, extensible, and easy to implement multithreading. The clojure threading macro provides language-level support for PurefunctionPipeline&Dataflow.
https://github.com/linpengcheng/PurefunctionPipelineDataflow
Both in programming and in real life haha.