Same here. Dug in a little and found that it's using a self-signed certificate. From an e-commerce site I might be worried, with this one I'm assuming the site is making a political statement about the centralization of control of trust on the internet.
"Data structure and functions should not be bound together": not if you don't want that, but often you do, to avoid constantly asking the question "where's the code that messes with this data?".
"Everything has to be an object": obviously bad, but also not the case in most OO languages. So...bogus objection.
"In an OOPL data type definitions are spread out all over the place": Huh? This one is made up. You can put all your <whatever> in one place if you want to.
"Objects have private state": this is a feature not a bug. Exhibit A: JavaScript.
I mean, OO isn't impervious to this: "Where is the factory to create this object?" "Where are the factorIES that create this object???" "Where is the service to perform this action on this object?" "Where is the manager that coordinates these objects????"
This can be solved with some knowledge of DDD (for example) which applies to both paradigms.
Personally I prefer when it's all objects. Otherwise you wind up with primitive types that you can't do all the objecty stuff with. Then you wind up with hacks like Java's int vs Integer dichotomy.
Compare this with Python where the internals of the integer type are so hidden that it can do things like seamlessly replace it with a bigint. What it is inside doesn't matter because you only care that it answers to the same messages.
What are your thoughts on project valhalla on the JVM? I think making structs object-like is a win as ultimately having everything involve extreme pointer chasing is wasteful
That just sounds like a code organization problem not an argument in favour of OOP.
> but also not the case in most OO languages
The fact that a lot of "OO" languages don't enforce this doesn't change the fact that the general sentiment around object oriented programming as I see it taught and as I see new programmers approach it is that "everything has to be an object."
The problem with OOP is not just OOP languages but also OOP programmers who you have to deal with on a regular basis and have to explain that "this could just be a function, why do I need to instantiate a class to do this basic task?"
> Huh? This one is made up.
Once again, this doesn't matter in light of how people ACTUALLY write code in those languages and what actually is the status quo.
> this is a feature not a bug
Not sure I know enough about javascript to understand what you mean here.
Hiding state inside things is fine when it's done correctly, more often than not in reality when people write object oriented code it is done poorly. I certainly haven't had the same troubles understanding what happens to state when reading someone else's pure functional code vs when I've had to read someone else's object oriented code.
>I think the lack of reusability comes in object-oriented languages, not functional languages. Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.
I think languages like F# and Racket get it right, because multiple paradigms are available to you.
I like F#’s stance on functional-first programming. There are times in which you want to expose the underlying types and there are times you do not. When I recently started a ray tracer implementation, it was a good example of this. The vector, color, point, transform, world, camera, etc. types where all readily implemented by records and discriminated unions which properly isolated but exposed the types. However, for the matrix library, I chose to use F#’s Array.Parallel library and thus a 1D array as the underlying implementation, and this was a perfect use case for using a class. I wanted to hide the implementation of the matrices from the user of the type and encapsulate the internal behavior, only exposing a clean API. Even in that case, the matrix type was immutable because the operations on matrices would simply return new matrices. I think F#’s acceptance of multiple paradigms is really the way to go.
Probably "State is hard—let's eliminate it!" is better if it is possible. Lots of states just shouldn't exist at first place because they are clearly derivations of some other state.
That's what college textbook OOP taught us. It only took me a couple decades to figure out that college textbook OOP taught us wrong. If all you're doing is hiding state, it's like sweeping dirt under the carpet - you're skimping on your work now, in a way that will only create more work later.
Good object oriented design goes much, much further. To quote Alan Kay, "Doing encapsulation right is a commitment not just to abstraction of state, but to eliminate state oriented metaphors from programming." (emphasis mine)
Not only that, but it’s pretty clear the philosophical roots of OOP are basically just FP with a particular organizational model. The idea was never “everything is mutable and gets to mutate everything else unless you say otherwise”, it was about organizing functions (real functions) with the specific data they operate on. The big blob of imperative programming that got bolted onto that is a disservice to the idea. But unfortunately that cat’s been out of that bag for decades and isn’t going anywhere.
> Not only that, but it’s pretty clear the philosophical roots of OOP are basically just FP with a particular organizational model.
That's true of much of OOP, but implementation inheritance is the elephant in the room - it can't be modeled or understood using a pure FP model, because the combination of late binding and so-called 'open recursion' requires a dispatch step on all virtual method calls (that would in turn be modeled in FP via a "tying the knot" trick). The resulting semantics are extremely tricky, and most practitioners are aware of the problems with them (see "fragile base class").
Some recent programming languages have abandoned implementation inheritance altogether, e.g. Rust. IOW, they're really more like FP-semantics languages with OOP-like syntactic sugar on top.
While you’re of course right that implementation inheritance can’t commonly be modeled in FP, it can be used in multi-paradigm languages with explicitly functional semantics devoted to it (eg Swift’s take on structs), and its purpose can be handled in FP languages either by data structures providing consistent interfaces into their constituent parts (exceedingly common in lisps) or by static types with similar consistency (exceedingly common in MLs).
In the latter cases it’s not inheritance in the sense that one thing derived from another, but that all the things you’d model that way are derived from broadly compatible base types.
Aside: Rich Hickey’s introduction to Clojure made all of my lisp anxieties vanish when he illustrated the syntactic difference as primarily moving a function call’s open bracket before the function name rather than after. It’s silly in hindsight, but it helped me feel more familiar at once and ready to learn the rest.
Un-aside: I think this kind of exercise would be valuable in translating functional-ish OOP (state is isolated but largely operated on with stateless functions) to its FP syntactic equivalent.
For example in Clojure, in Clojure you could syntactically rewrite (map some-hash some-fn) as a map method on a class instance and it’s just moving some punctuation around.
Another one which is probably not very mind blowing here, but did tickle my brain when I recognized it for what it was: before the great concurrency upheavals over the last decade+ (eventually settling on Promises and async/await), Node had monadic Either/Option types as a core part of its interface (you just destructure them as callback arguments).
None of this is meant to disagree with anything you said of course. Just wanted to add some “if you’re approaching state the same way there’s a representation in your environment” flavor to the discussion.
My claim was specifically about implementation inheritance. Providing 'consistent interfaces' falls under interface inheritance (which is non-problematic), and 'inheriting' static data types only is semantically indistinguishable from composition. The problem with OOP is also how it conflates patterns that have very little to do semantically with one another as one overly general thing ('inheritance!') even though the general case is not really useful.
I understood your claim for what it’s worth. That’s why I cited Swift structs. They’re objects in the historical sense, functional in the immutable sense, and provide explicit inheritance semantics. The rest of my response was about achieving the same purpose without implementation inheritance.
Abadí and Cardelli's ς-calculus is a pure FP model that's almost nothing but implementation inheritance, and it's the best starting point for modeling those semantics, at least if what you want to do is design a type system for an OO language.
The basic rewrite rules are almost as simple as the λ-calculus; as I remember the notation, it looks like this:
e.m → body[e/i] where m = ςi: body occurs in e
e{m = ςj: c} → {stuff, morestuff, m = ςj: c} where
e was {stuff, m = anything, morestuff} and
there was no definition for m in stuff or morestuff
The expr[replacement/variable] notation implies not only replacement but also α-renaming to avoid variable capture just as in the λ-calculus.
You can, for example, define the Boolean values true and false as respectively
assuming you have a suitable interpretation of "1 + expression". And, if not, you can rewrite that to something like one.plus { argument = ... }.result, with a Church-numeral-like construction if you're really enthusiastic.
I think the ς-calculus is a lot more ergonomic than the λ-calculus in practice, and I've written things like string-parsing code and vector arithmetic libraries in it, or rather in a programming language I implemented called Bicicleta, which is a thin layer of syntactic sugar on top of the ς-calculus, so you can write things like foo(bar, baz) instead of foo { argument1 = ςx: bar, argument2 = ςy: baz }.result and 3 + 4 instead of 3.'+' { argument = 4 }.result. But it's just syntactic sugar.
I'm still not sure if this was a good idea because I'm really skeptical of whether inheritance at all is a good idea. But if it's a bad idea, it's not because it rules out having a pure FP model or even makes it extremely complicated. It's already common to augment the λ-calculus with things like records, arrays, algebraic data types, even generalized algebraic data types, and Haskell-style typeclasses, any of which add a great deal more complexity than the tiny increment in complexity added by using the ς-calculus as a basis.
However, especially (but maybe not only?) in a dynamic language like smalltalk or ruby, you can simulate implementation inheritance pretty closely with just composition and (some kind of automated/macro'd) delegation if you want to. I'm not sure how/if that changes things at a theoretical/formal level.
Yeah, Alan Kay once likened OOP to biology saying the ultimate embodiment is replicating “messages” between cells.
Once I heard that, it makes natural sense that Combine became a first class library in Swift, giving the code base ion channels so that messages just aren’t sprayed everywhere with NotificationCenter or requiring you to name each individual “protein/hormone” message in your program.
That Alan Kay quote is intriguing to me, I haven't heard it before, and I'm not sure I understand what it means.
I found that it's from this piece, although a brief skim I'm not this piece gives me more guidance about how to do that, but I plan to spend more time with it.
I like this a lot as I run into so many arguments about stateless this or stateless that, and what gets lost is that not contending with state is problematic. Databases are forced to get massive and do all sorts of goofy shit so some people don't have to ever think about state; this goofy shit creates interesting reliability challenges which translates to bad user experiences.
>Databases are forced to get massive and do all sorts of goofy shit so some people don't have to ever think about state; this goofy shit creates interesting reliability challenges which translates to bad user experiences.
Huh? Databases don't do anything "goofy", they implement ACID guarantees.
Which absolutely beats the untold horros of Chtulu proportions that would emerge if programmers had to implement state management at the persistence layer for multiple clients themselves (they'd implement a DB + ACID poorly and in an ad-hoc way, full of holes).
What people seem to forget is that relational databases are not the first technology we had for periststent state. The first versions were custom solutions per app, application-as-state and various ad-hoc such databases with their own formats and techniques, which were such a hellist landscape, devs jumped at relational databases with utter joy...
> Huh? Databases don't do anything "goofy", they implement ACID guarantees.
I think that he meant that functionality that should not is pushed into database. I have definitely seen project full of crazy triggers and sql procedures and so on.
That only matters for interfaces which you expose to clients. The real problem with getters & setters & such is when they gum up the internal workings of a codebase, abstracting over nothing and complicating things unnecessarily.
I agree with that. If you create accessors as a matter of course, then you are mostly adding dead weight. But accessors can add a useful layer of abstraction at the interface layer, as you say.
not quite! a property is a pair of methods. if you replace a member with a property, it does remain code compatible as you describe, but breaks the ABI. so if you do this in a published library, clients require a recompile. for this reason, they recommend:
1) different naming convention between members and props
2) dont expose members; use properties from the start for anything public
to assist with two, they introduce the default get/set implementation, like so:
public object Derp { get; set; }
for when you want to expose an interface with the assignment idiom but dont actually have any interesting implementation
Getters and setters aren't necessary to OO. They're one OO-compatible way to implement data access control. Inheritance is not about where you store the state, but behavior polymorphism. Factories are also not about where to store state, but about dependency injection. (Arguably they are not the right way to do it but one thing we can say for sure is that they are not there to solve the problem of where to store state.)
Not only are getters/setters not necessary, they actively work against good object orientation. The whole point of OO was to work with things in terms of their _behavior_ and not their _data_. The way OO is taught by creating data-centric classes has misled generations of developers into thinking they understand encapsulation by making a variable private and then providing a getter & setter for it.
I spend a good part of my time training Java developers away from this idea in order to help them make their code more testable and understandable. Queries and Commands are not just pedantic alternate names for Getters and Setters: thinking about objects with respect to what you can ask it to do (Command or Request) and what you can find out from it (Query) significantly improves the code that gets written.
1. Are only conventions. Nobody's forcing you to use them.
2. Simplify refactoring.
3. Are a necessary layer of abstraction any time that 'setting' a value requires updating a related value.
#1 and #2 are only a manner of taste and implementation-specific use cases of tooling built on top of a language. #3, however, is absolutely necessary, to avoid entire categories of business logic errors.
Just imagine if updating a string's value required you to also manually update the string's length. How many string-length related bugs do you think that kind of adventure will result in a typical program?
I don't say that a string has to be mutable, I am saying that when you assign a value to a string variable, such as during its first (and let's say 'only') initialization, it would be absolutely insane to also have to set the string's length.
Arguing against setters is like arguing that
String s = "foo";
must also be accompanied with
s.length = 3;
The two operations make zero sense to be done separately - doing so is just a minefield of bugs. The purpose of a setter is to combine similar inseparable operations together.
A trivial pass-through setter obviously provides ~zero value, but it also carries with it ~zero cost. Any compiler worth its salt will optimize its invocation away.
> I am saying that when you assign a value to a string variable, such as during its first (and let's say 'only') initialization, it would be absolutely insane to also have to set the string's length.
You should have a constructor/factory for strings that will form/instantiate the string in a valid state, with its value matching its length. That's not the same thing as a setter that mutates the string in-place.
The purpose is ergonomics essentially. Technically you don't need a high level language or even assembly and could work just in machine code with all of the other organizational description of it documented elsewhere.
The real question with all features is "Does it make a given task easier, better, or possible Y/N?" Said question is incredibly vague by design and should differ by situation.
Well the common FP answer is that data might have many operations on it that are totally orthogonal and that don't depend on shared behavior. I say this as someone who thinks both paradigms have something to offer.
Namespacing them together is good. Hiding the coupling between them isn't. If a function makes use of some data, you want to see where that data comes from, not have the function fetch it via a magic portal to some other part of your code.
This is true, and it’s the primary reason I’m not finding some way to work in Haskell. But it’s also kind of a cop out.
The issue isn’t just that state exists and will do well beyond our extinction. It’s not even just about how you manipulate state or how many restrictions you put on mutability. It’s also about the visibility of state and stateful processes, how that’s signaled, and the conventions that fall out.
This isn’t something I fully understood until I worked with Clojure. Immutable-by-default was a fantastic constraint. But more valuable to me was that where state is accessed or changed, you can tell immediately.
Just having that gives you several insights:
- This code is more likely to be impacted by outside behavior
- This code has additional concurrency requirements
- This code may be unnecessarily complex
- This code may be hard to understand a week or more later
- This code is documenting its implicit dependencies
Since I’ve moved on from Clojure I’ve mostly worked in TypeScript, and I’ve done my best to apply the same principles.
In some ways it’s a loss: you can’t signal stateful access with @ or ! or *.
But that’s all convention. In other ways it’s a huge win: if you’re trying to write functional code in TS, you eventually end up with the idiom that async/await and Promise types are generally signals of state. And the type system calls that out much more reliably.
All of that said, you do gotta stick the state somewhere. But where and how you do is the difference between eternal pain and eternal much-less-pain. And coupling state with functions on objects is definitely more likely to produce more eternal pain.
Fyi... the previous submission 2 years ago attracted 380 comments and the top comment says Joe Armstrong changed his mind on some of it. Apparently, the original blog post is actually dated 2000 and not 2019.
For people on mobile, here's Armstrong's quote, via @rhblake:
"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."
Sure but Smalltalk also has classes and inheritance where messages are passed between objects. Kay invented the term, but he doesn't control the design of other languages or how OOP ended being understood. Simula also proceeded Smalltalk, and it influenced the design of the C++ object system.
This reads like someone using functional arguments to disparage OO. It also sounds like someone who hasn't written enough OO to understand its benefits.
Thanks, This it's one of those discussion people keep having at nausea, one of those simulacrums people invent so they have something to feel superior about.
Years ago, I maintained (tongue in cheek) that OOP was a great paradigm not because it was inherently better, but because it was so bad that you had to rewrite your code several times to make it work. And once you've written something three or four times you begin to figure out your mistakes.
Then that Gang-of-Four "Design Patterns" book became popular and really screwed things up. I swear I didn't see factories-making-factories-making-factories until that thing was published, then I couldn't go a day without encountering yet another SingletonFactoryWorkerVisitor or whatever was cool that week, ugh.
I'm joking, of course. Except about Gang-of-four. And rewriting your code.
> I swear I didn't see factories-making-factories-making-factories until that thing was published
No? That's a pretty good description of the sort of metaprogramming that Lisp gives you, and Lispers are really enthusiastic about that kind of thing. People will dispute the parallels, but the real difference comes down to:
- non-Lisp languages being significantly less powerful
- Lisp's veneer of respectability
... which shouldn't be all that odd, considering the person who gave us "design patterns" was Dick Gabriel, a Lisper.
At least the "SingletonFactoryWorkerVisitor" programmers use a nomenclature that reflects the ontology the object is supposed to fit into.
My guess is that LISP programmers are largely a self-selected group of pretty decent hackers. I'm basing this on some exposure to Lisp Machines in the 1980s, and some Common LISP stuff in the 90s. Generally adults. LISP is my favorite language I'll never ship a product in (well, okay, after SmallTalk :-) ).
The Gang-of-Four-driven stuff I saw in C++ and Visual Basic (late 90s and early 00s) still gives me the shivers. Like, "Hey, factories are cool, let's make a bunch of them for no reason because we might need the flexibility someday." Sigh.
There is an effect where some things work well on some languages, but fail completely when translated to others because of superficially unrelated features.
Compile and run time code generation seem to be one of those things. Having a mostly pure language (even when there aren't guarantees) makes them much saner.
1 - Pre-compile time generation, by its turn seems to never work very well. And if there is developer interaction between the code generation and compiling, than it's always a disaster.
Well goodness, I wrote a factory factory factory in OCaml only eight days ago:
(* A goal that succeeds if v is one of `terms` *)
let rec amb (terms : term list) (v : term) = match terms with
| [] -> null
| term::ts -> disj (match_goal v term) (amb ts v)
and null (s : state) = Mzero (* a goal that always fails *)
and ...
For those who don't speak OCaml (whose syntax is really quite unintuitive), that's a function named `amb` which takes a parameter called `terms` of type `term list` (in C++ syntax, that would be list<term>). It returns an anonymous function that takes a parameter called `v` of type `term`. This function may call `disj`, or it may return `null`, which is not a keyword, but actually another function I defined; it takes a parameter named `s`, which is of type `state`, and returns Mzero, which is at long last an honest-to-God singleton constant. (Of the parametric type `α stream`, as it happens, but let's not get distracted with that.)
So `amb` is, in object-oriented parlance, a state stream factory factory factory. Upon being passed a `term list`, it returns a factory, which, upon being passed a `term`, returns a factory, which, upon being passed a `state`, returns a `stream`. (I only traced the `null` path in this comment, but the `disj` path also returns a stream, specifically a `state stream`—OCaml's static type checking guarantees that both return paths return compatible types.)
You might think that isn't the way it's actually used, but my example code uses it in precisely that way:
Here `amb` is being invoked with just a `term list`. (OCaml separates list items with semicolons, so that commas always make tuples.)
So I think factory factory factories are very useful, but also I think they're really hard to keep under mental control without a functional language with a strong static type system. How on earth would you translate those four lines of code above into Java? With four inner classes, maybe, nested eight levels deep with {{}}?
If you're interested, I was translating μKanren, a Prolog-like logic-programming language originally written in 39 lines of Scheme, into OCaml: http://canonical.org/~kragen/sw/dev3/mukanren.ml
A factory factory factory would be a bag of hidden mutable state that could construct another bag of hidden mutable state that could construct another bag of hidden mutable state that could construct another bag of hidden mutable state, making it virtually impossible to track down where anything in the final result had come from - things could come from any mutation to the final result, or some but not all mutations to any of the intermediate results.
Without the mutable state it's not a factory, it's just a function, and there's no confusion and no problem.
I don't agree with either of the claims in your comment.
Although the GoF Abstract Factory pattern doesn't rule out having mutable state in your factory object, that is neither necessary for its purpose nor common practice when applying it in OO programming languages. The example in GoF is MotifWidgetFactory (for shitty Unix GUIs) vs. PMWidgetFactory (for shitty OS/2 GUIs), and in fact GoF says, "An application typically needs only one instance of a ConcreteFactory per product family," suggesting that they were thinking of cases where the factories don't even have immutable state such as the `terms` argument to `amb`.
They do explore a "prototype-based" factory that is stateful—but there the statefulness is just used as a way to configure the factory at startup, by building up a "product catalog" in it. Creating products doesn't modify the factory's state, and you could configure the "product catalog" just as easily by passing it in as an argument when the factory is instantiated; it's just that, in Smalltalk, that kind of thing is normally represented by segments of executable code that invoke a long sequence of side-effecting methods, rather than by data structures.
(You can also represent that kind of thing invoking a long sequence of pure functions, as in the "fluent interface" pattern that became popular with JQuery, but that isn't the Smalltalk way to do things, especially in 01994.)
The GoF examples other than the prototype factory are WidgetFactory and MazeFactory; in InterViews, WidgetKit, DialogKit, and LayoutKit; and in ET++, WindowSystem. Kerievsky's examples in Refactoring to Patterns (where he points out that people constantly conflate the various "factory" patterns, and that the lines between them are somewhat vague) are HTML parser AST node creation, ORM attribute descriptor creation, java.util.SynchronizedCollection and UnmodifiableCollection, and an OutputBuilder interface that allowed him to create either DOM nodes or XML output from the same unit test code. In all of these cases except the OutputBuilder, the factory objects are stateless.
So, by your definition that a factory is what you get if you take a function and add mutability, we find that 6 out of 7 of the GoF examples of "factories", and 3 out of 4 of Kerievsky's examples of "factories", are actually "functions" rather than "factories". I think this clearly shows that the categorization you propose does not coincide with the scriptural explanation of what "factory" means.
⁂
It's true that, in the case where there's no state, you can do this with just a pointer to a function. But pointers to functions don't exist at all in languages like Java, and aren't first-class values in languages like Pascal, where they are subject to various restrictions that keep you from using them like this.
Even in languages like C, which have first-class function pointers but not closures (except as a GCC extension subject to Pascal-like restrictions), you can't create new function pointers at run-time. So in C, or C++, you can replace a singleton factory like the ones GoF considers usual with a function pointer, or maybe a record of function pointers. But you can't write a factory factory that way, much less a factory factory factory.
So, while you're right that in a sense an immutable factory "is just" an indirectly-invoked function—that's the main point of my comment—if you want to do that in a language like Smalltalk or C++ (at least in 01994, when they wrote the book) or Java (at least in 02000), the Abstract Factory pattern in GoF tells you how to get it working.
> Although the GoF Abstract Factory pattern doesn't rule out having mutable state in your factory object, that is neither necessary for its purpose nor common practice when applying it in OO programming languages.
An object by definition encapsulates mutable state. Even if a particular object happens to not contain any mutable state, there's no way to observe that from outside, by design.
If your language forces you to use an object to represent an (immutable) function value, all I can say is don't use such a shitty language.
> An object by definition encapsulates mutable state. Even if a particular object happens to not contain any mutable state, there's no way to observe that from outside, by design.
If I read you correctly, your definition of "object" excludes each of the different concepts that C++, D, Python, and Abadí and Cardelli's object-calculus call "objects", but includes what Scheme, OCaml, and Kotlin call "functions"? You are of course welcome to use that definition, but it might improve communication to clarify that you're using the same word that other people use but with a completely different meaning.
> don't use such a shitty language.
There's a plausible reading of the GoF book and the whole "OO patterns" movement that it's largely about how to make do with shitty languages, though I'd nominate SICP as being better at that. Often languages that are shitty on one axis have compensating advantages on other axes—for example, although you can script Cocoa with cffi in Python, which is less shitty than Objective-C for scripting in general, you waste a lot more time debugging segfaults; despite the admitted aesthetic advantages of 68000 assembly with respect to amd64 assembly, the latter performs noticeably better on this laptop; and, while I think I've convincingly shown that a factory factory factory is dramatically more readable in OCaml than in Java, you're probably going to have a much easier time getting your remote dictionary server performance to scale to 16 cores in Java than in OCaml. If you have enough RAM, anyway.
> If I read you correctly, your definition of "object" excludes each of the different concepts that C++, D, Python, and Abadí and Cardelli's object-calculus call "objects", but includes what Scheme, OCaml, and Kotlin call "functions"?
That's not my intention; I'm trying to match the definition of an "object" in common programming languages. As far as I can see an object (instance) is essentially a bundle of method handles associated with an opaque (possibly empty) bundle of (possibly) mutable state. (In some languages the bundle might also include some visible state, but that doesn't change the essence of the thing, since that can be emulated with method handles - indeed Kotlin does exactly this). In Python's case the opacity of that state is a convention rather than a strict rule, but I think the general consensus would be that it should generally treated that way, and also that Python's objects are somewhat less object-ey than those of other languages. Certainly I'd say this definition is a close fit for C++ and Kotlin; what are you seeing as different there?
Hmm, I had thought that Python didn't allow you to add data attributes to objects derived from immutable classes like `tuple` or `str`, but evidently I was wrong about that!
>>> class S(str): p = lambda me: len(me) + 3
...
>>> S("hi").p()
5
>>> S("uhoh").z = 3
>>>
You can't even detect this by enumerating the object's attributes with `dir()` or `.__dict__` at some moment — a method, or external code, can add new attributes at any later moment.
(Hypothetically in CPython maybe you could disassemble the bytecode of a method to see if it tries to mutate the object or some global variable, but even if this is possible, you can also use cffi to change the value of 3 in CPython. I regard both of these as breaking the semantics of Python per se.)
So, I was wrong about Python. And, on further reflection, I was wrong about C++ as well, because even if you provide a `const`-argument override for a function, which is a thing you can do to observe whether an object is `const` or not from the perspective of your caller:
int x(int* foo) { return *foo + 3; }
int x(const int* foo) { return *foo + 2; }
// declaring as `const int i` changes program behavior:
int main(int argc, char **argv) { int i = 7; return x(&i); }
there's not even a compiler warning if your caller fails to do so as well:
int x(int* foo) { return *foo + 3; }
int x(const int* foo) { return *foo + 2; }
// int y(int* foo) { return x(foo); }
int y(const int* foo) { return x(foo); }
int main(int argc, char **argv) { int i = 7; return y(&i); }
D's `immutable` is externally observable in the same way as `const` in D or in C++, but rigorously so in the sense we want here: invoking a function whose parameter is `immutable` with a mutable object is a compile-time error.
Also, D's `immutable` is transitive, unlike C++'s `const`, but like, for example, E's DeepFrozen.
In the simple object calculus, everything is an object and there is no mutability at all. You could reasonably object (heh) that, although it's intended to capture object-oriented programming, and Abadí and Cardelli's book is called A Theory of Objects, it's not widely used, so opinions may differ as to whether they succeeded. https://news.ycombinator.com/item?id=26588642 goes into more detail there.
So your definition of "object" only excludes each of the different concepts that D and Abadí and Cardelli's object-calculus call "objects" (and I guess I should add E), not Python's or C++'s as well.
As for Kotlin, my claim about Kotlin was not about Kotlin "objects" but about Kotlin "functions", which are closures that can include access to mutable state, just as in Scheme and OCaml. I don't know Kotlin well enough to know whether your definition of "object" fits its definition of "object". It's an interesting question. Maybe it does! Kotlin's data classes, for example, can contain mutable attributes, and there really is no (non-reflective) way to observe them from another class — for example, for a method to refuse to accept an argument if it happens to be mutable in this way. Kotlin/Native evidently has a form of transitive and externally observable immutability, but I don't know much about it.
Your revision of the definition to include a bundle of method handles does seem to neatly exclude what are called "functions" in Scheme, OCaml, and Kotlin. I think it might also exclude objects in E, but since E is dynamically typed, that's sort of an implementation detail, and anyway E doesn't figure prominently in the consensus definition of the term "objec...
I think Design Patterns is often painfully misunderstood and misconstrued. My idea of using the factory pattern, for example, is not to write a MarriageFactory class, but to write a Celebrant class, and we may recognise that it embodies the factory pattern if that is helpful to the comprehension or authoring of the code.
And so it goes with the others, many of which I use extensively in my own work, but at most there's a comment at the top saying, for example "this business rules structure is in the chain-of-responsibility pattern".
That is the material use of patterns, they are not abstract frameworks. Naming things makes a difference: encouraging a taxonomy of purpose, rather than of form, goes a long way to re-orienting a programmer towards problem-solving with concretions rather than abstractions. As a consequence, Design Patterns, along with Refactoring and PoEAA, continues to serve as one my most-thumbed indexes of solution ideas.
The only thing that changed after that book is that people started to put "Factory" into the name of factories and "Decorator" into the names of decorators.
Both existed for a long time before, but did not had names that would clearly instantly made them recognizable as such.
The funny thing is that rewriting your code a lot is much easier on FP with flexible types.
You just need to not pay any attention to design and start coding right away. Then you'll rewrite your FP code even more than you did for your OOP code, and still have an easier time overall.
In all seriousness, I've found that a very nice way to develop FP code. It always looks good at the end anyway, and there's no risk of getting into astronaut mode.
What if all this... A sucks use B... Is just... Different strokes for different folks?
Meaning, due to our brains being differently wired some forms of programming might be easier to understand and use for some people while other for others
If you assume "brain wiring", then you're basically making the case that everything is relative, and there's really no way to improve things beyond the state they're in. Programming can never get any better, and languages can never improve, because it's all different strokes for different folks.
But we definitely know how to make things worse for just about everyone. Use brainfuck. Or Piet. Or a straight-up turing machine.
And if we can make things (pretty much) objectively worse, then it's very likely we can make things objectively better. And there's not much reason to assume we've already reached some kind of end-game, where we're at the tip of the spear for language design, or even the core fundamental models of design.
I mean shit, we're only like 60 years into the subject. The legendary programmers of lore still walk the earth.
It might take some rewiring, but I for one believe there is a much better world for us to program in, than C, Java and C#.
I think much more likely than different "brain wiring" is universal brain damage, from working, breathing, living, with the languages we know, love and despise today. Brain damage might be harder to fix however; in which case we're dealing with language advances one funeral at a time.
As soon as someone identifies an enterprise programming paradigm that is better for 100+ software engineers touching the same codebase, OO will be replaced.
Many of the theoretical advantages of OO in low level programming low level don't apply to modern, high level languages, however the encapsulation, convention, and principles (SOLID) of modern OO development are unassailable by current alternatives as the best way to throw that number of software engineers at a single project.
I like Elixir and Erlang so agree with the the typical arguments against OOP, but I actually think value-based (as opposed to reference based) OOP works quite well and jives with functional programming. The best examples of this, that I know of, are LabVIEW, with its dataflow and value-based OOP that includes interfaces, and F#’s object programming, where immutable types such as records and discriminated unions can implement interfaces.
I rarely see points like your’s raised (reference based versus value based) in the OOP vs FP debate, but these nuanced points really play a big role in the ability to produce quality code. I have constantly gone back and forth on my opinion on OOP vs FP, and it mostly comes down discovering an expressive feature of a new language tipping me back to the other side. Using an OOP language with structural typing was one of these instance. Structural typing isn’t really an OOP thing, but that highlights how blurry the lines can actually be as to what is considered as OOP vs FP.
Armstrong's death was one of the few that I was sort of hit by, despite not knowing him personally. I've watched so many of his talks, read as much as I could from him, and I've generally found him to be quite an inspiration and seemingly a great guy.
More on the point of the article, it's sort of fun to think about the fact that "OO Sucks" is sort of ironic given Alan Kay's initial description of OO was closer to actors than what we think of OO today (he acknowledges this at a later time).
As Kay puts it:
I'm sorry that I long ago coined the term "objects" for this topic because it gets many people to focus on the lesser idea.
The big idea is "messaging"
[..]
The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.
[0][1]
This is very much in the spirit of Armstrong's quote in the article:
> Since functions and data structures are completely different types of animal it is fundamentally incorrect to lock them up in the same cage.
Armstrong talked a lot about how shared mutable state was wrong on a fundamental level - it "breaks reality(/causality)" and that sort of thing. Again, sort of fun to think about the fact that the core ideas with actors seemed to have an origin in an early focus on asynchronous 'cell'-like computers, like the JOHNNIAC in 1953[2], even though the foundation of the model wasn't named or formalized until the 70s.
"Its designers began with the hope of stretching the mean free time between failures and increasing the overall reliability by a factor of ten". Systems like JOHNIAC where IAS machines - asynchronous CPUs. These worked through causality, not synchronization.
In Armstrong's thesis[3] 'Making reliable distributed systems in the presence of software errors' he references papers like [4] 'Why do computers stop and what can be done about it? Technical Report 85.7, Tandem Computers, 1985.', which talks again about isolated processes and transactions as a foundation to reliable computing - over 30 years later.
This whole area is so deeply fascinating with a century of repeating, refining ideas, and I found that just by reading what I could from Armstrong I had a sort of rough guide through this area. There are these cool ties to early ideas about AI, and I guess a lot of people though that languages should model life, and later Kay talks about this as well, and funny enough AWS now has "cell based architecture" - their discipline built around isolation and fault tolerance. A lot of this is sort of just random connections from jumping from paper to paper - but I just found it all really cool.
Reading this thread, it almost seems like people don't know who Joe Armstrong is? Or at least they've missed a lot of the point. This isn't an "X vs Y" from some rando, he built Erlang. It's also not about functional programming.
I highly recommend reading what he has to say, and watching his talks.
In case you don't already know about it; Handbook of Neuroevolution Through Erlang by Gene Sher is a great demonstration of the approved way of using Armstrong's ideas.
Thank you! Unfortunately the part of my career where I had a lot of time to dive into this sort of thing is behind me (and hopefully ahead of me too), but I'll add that to my backlog.
As a professional dev who has made a career out of working in "OOP" languages and codebases, I kind of agree. The reason why Functional Programming concepts are increasingly adopted by "OOP" languages is because Functional Programming is objectively better.
What makes functional programming objectively better? Are there studies which show this to be the case? What does better mean? Better for some people who prefer the functional approach? Better for some languages which support functional programming? Better in some situations which functional programming is well suited? Or just always better for everyone?
Just look at all the "OOP" languages where both the language developers and the user community are coming around to the facts that
* immutability and absence of state is preferable to mutation and statefulness
* Option/Maybe types (or "nullable types" which are a shoddy implementation of the same thing) are better than null
* Either/Result types are better than exceptions
* making things implement map, filter etc and sending in a function that describes what you want to do is better than manually eg looping through lists etc
etc etc etc
Anyone who doubts how endorsed this is, just read what Brian Goetz and Josh Bloch have to say about how to code in Java.
Just imagine if these languages had been implemented with these ideas in mind from scratch instead of the current situation of trying to adopt and retrofit this style when the core libraries fundamentally don't support it.
The current trend of "OOP" languages is basically inexorably heading towards FP and abandoning the old school "OOP" style. Eventually they will only be nominally "OOP", mostly in order to please people who have irrational attachments to labels like that, but be way more FP in nature and in all but name.
For what it's worth, people shouldn't be irrationally attached to the "FP" label either. Labels aren't important - what matters is the code, how easy or hard it is to reason about it, how well it avoids entire categories of defects from even being possible etc etc.
> Data structure and functions should not be bound together
Maybe not always. I suspect, based on Kay's writings, that Smalltalk's "everything is an object" thing was more about experimentation. They were trying to push one simple idea as far as possible, in order to see just how far they could push it. It turns out you can go pretty far. That doesn't necessarily mean you need to.
That said, the most popular language that shies away from "everything is an object" - Java - does so in a way that doesn't work well. Primitives are not objects, sure, but primitives then integrate poorly with the rest of the language (especially since Java 5), and more complex data structures must be objects, even if you don't want them to be.
Ironically, my favorite language for showing the nice things you can get by relaxing the "everything is an object" ethos, F#, actually does make everything into an object. (It has to, because .NET.) But it doesn't force you to think about them as if they were objects when you don't want to. So the mental space you live in when you're using the language feels primarily functional.
But sometimes objects are useful. The key distinction here - and the thing that Armstrong seems to miss in this essay - is that methods aren't just functions that have been glued to some data. Technically I suppose they are - that's certainly how it works when you roll your own OOP in a language like C - but it turns out the whole is more than the sum of its parts. Because you get a really useful thing that isn't so convenient to do when you keep your functions and your data separate: dynamic dispatch.
Dynamic dispatch - not encapsulation - is the killer feature of objects. Late function binding allows you to operate over heterogeneous inputs where the only thing you want to enforce is that they obey a protocol, without having to do mess of explicit conditional branching or having to have all the details pinned down at compile time. Functional languages without any OOP facilities run into convenience problems here. Typeclasses cover some of the same use cases, as well as others that objects and interfaces don't handle very well at all, but they're statically bound, so they can be somewhat less flexible. It's often stated that design patterns are just a way of making up for a missing feature in your language. Well, the command pattern is what you do when your language doesn't support OOP.
(For another example of where Armstrong was clearly operating under some misapprehensions when he wrote this, look at the truly bizarre statement he makes in the last paragraph of section 3.)
Yeah an example of typeclasses being less flexible to me is if what you really want is OOP polymorphism for something like being able to choose what code to run based on configuration. Given that you can add objects to your class path at runtime, you can clearly get some extreme flexibility out of a system without clear parallels in typeclass based polymorphism. Obviously this isn't something you'd want to do all the time, but it's an example of something I think about periodically when comparing the two approaches.
With typeclasses, polymorphic behavior is driven strictly by the type, and one must follow that thought to it's conclusion to see the difference. Sometimes it's more useful, sometimes not.
> Late function binding allows you to operate over heterogeneous inputs where the only thing you want to enforce is that they obey a protocol…
You can do that in typeclass-based languages as well, e.g. in Haskell:
{-# LANGUAGE ConstraintKinds, ExistentialQuantification #-}
data Constrained c = forall a. c a => C a
showAll :: [Constrained Show] -> [String]
showAll xs = map (\(C x) -> show x) xs
main = foldMap putStrLn $ showAll [C (1 :: Integer), C ("abc" :: String), C (3.14 :: Double)]
The `showAll` function takes a list of objects that implement the `Show` typeclass. The objects themselves can be of different types. The syntax is slightly less ergonomic, but the runtime effect is much as you would expect from an OOP language—a pointer to the typeclass dictionary ("vtbl") is paired with each object, and the address of the appropriate `show` method is looked up at runtime for each element of the list.
Are there non-object oriented languages that are popular today? Python, Ruby, JavaScript, even PHP.
I’d like to hear some things from people who only started programming in languages like that and know no other way, but have maybe learned C or something.
I picked up languages like that first and then picked up c later.
I tend to miss lambdas more than classes in c.
I'm not big on large class hierarchies, so classes tend to be more about conceptual organization to me. And throwing a bunch of related function pointers into a struct and passing a pointer to the struct itself almost kinda looks like a class if you squint hard enough. It keeps everything organized.
Packing everything I need into a custom struct, coercing it to a void pointer, and passing it somewhere to emulate a closure, on the other hand, feels dirty.
Well, to the extent that TIOBE is good for anything I suppose it's good for this. The #1 language for March 2021 is C, a decidedly non-OO language. #2-8 are all OO languages or languages that promote an OO style. #9 is assembly (really?). #10 is SQL, hardly OO, it's a declarative language with no pretensions of OO-ness that I've noticed. #11 is Go which is only kind of an OO language, but not really in the sense most people mean when they discuss OO. Of #12-20, most are arguably OO languages of various flavors except for R, Perl, Matlab, and maybe Classical VB (only because I don't know exactly what they mean by that, how far back do we have to go to get to Classical VB? The fall of the Roman Empire? Is it primarily used with an abacus?). Below the top 20 down to 50, the remaining languages are mostly not OO languages, or their OO components are best considered a secondary or tertiary feature (Ada, for instance).
So, yes there are popular non-OO languages today. No, they probably don't dominate in overall interest or use, at least outside certain domains. But people are even putting Javascript on microcontrollers these days so it won't be long before it's OO everywhere. Hell, OO (via Java) has already been to Mars.
>> and maybe Classical VB (only because I don't know exactly what they mean by that, how far back do we have to go to get to Classical VB? The fall of the Roman Empire? Is it primarily used with an abacus?)
funny enough I worked at a place a year ago who’s main language was VB6 and they were terrified of objects. Mostly exposed to it on the web side with python and php written by two new developers
Oddly some of their best code was some VB6 using OOP that someone had snuck in sometime years ago.
Python and PHP aren't object oriented languages. They are structured languages with optional objects.
Javascript is object oriented but has it's own definition of "object" that differs from every other language, so people try to abstract it away and write most of their code in some other paradigm.
I have no evidence other than the observation that OO mimicked the taxonomy used in the natural sciences. Example: Homo sapiens is of the class mammalia and of the kingdom animalia. This taxonomy originated from Aristotle to describe the world. [1] The problem is that software is needed not to take a static snapshot of the world (state). Software is needed to automate and report on changes (transactions).
Aristotle wrote about change and thought about change in four ways. The material from where object came from. The form or template or shape of the change. The efficiency or agent that caused the change. The purpose or reason or final cause of why the change was made.
So instead of classes and methods. If we followed this paradigm, we could be now discussing forms and changes.
Of course business folks would think this is all silly. They speak the language of accounting which records transaction entries in a journal. Each month the books are closed and the transactions are summarized as the closing balance. Instead of classes, the talk about general ledger codes. Instead of methods, they talk about entries and reverse entries to correct an error. Instead of state, they talk about auditing the entries to verify the balance.
OOP (as Alan Kay conceived it) was explicitly inspired by biology. Objects are cells and communicate through exchanging messages. State is local and hidden, and data itself disappears, which means that the program as written may be ignorant of how operations are performed.
I'd suggest that OOP has it's niche. Primarily it's a methodology for limiting the effects of changing state while keeping the codebase performant. Removing state where possible is obviously going to be the safer course of action, but immutable data structures are not as efficient as their mutable cousins. This may not matter for much software, but where it does, OOP isn't the worst solution when used selectively.
The problem with OOP in 2021 is that it tends to be the default, when arguably starting from safety and working back toward performance would be a better approach.
"Safety" and "performance" are not always the most important considerations. For example, Apple uses OOP so that its frameworks can evolve without breaking client apps. NSDictionary is a dynamic object because it permits the implementation to be changed or replaced, and this comes at a cost of performance.
Right. But OOP makes it central, and builds around it, while FP de-emphasizes it in preference to ADTs.
Strings are a good illustration. Instead of an abstract polymorphic String type, Haskell provided a concrete String type as an ADT. This proved too inflexible, which is why we have Text, lazy Text, ShortText, etc. Compare to NSString which also has multiple representations but hides them behind a single polymorphic interface.
It does not. -XOverloadedStrings unlocks `fromString :: String -> a.` That is not a polymorphic string interface; it's just syntax sugar for making something else from a string literal.
"OOP makes it central, and builds around it, while FP de-emphasizes it in preference to ADTs."
ADTs are not an intrinsic part of FP, as not all FP languages even have them.
I'd also question whether ubiquitous polymorphism is overall a good thing in a language, or whether it's misguided complexity. In most OOP languages, any public method can be polymorphic, but a polymorphic function is inherently less predictable than one dispatches off a single type.
I agree with Martin Odersky that functional programming within an OO approach to code organisation is the most sensible trade off between maintainability and bug reduction.
A bunch of these objections are incorrect, or at a minimum apply only to certain implementations of OOP (both from an organizational and linguistic perspective).
"Everything has to be an object" is only true in some languages, and even then it's a questionable claim. If I have a Java class that has public data members only, how is that materially different from a data structure?
"Data type definitions are spread out all over the place" Again, a questionable assertion. It's plenty easy to define all your data types in one package or header file in Java or C++. Some folks choose not to organize it that way, but that's a different and substantially weaker objection.
"Objects have private state" Unless you want all parts of your system to be able to depend on the internal state of all other parts of your system, some kind of state access control is going to be necessary. I'm not aware of any system, OOP or not, where the internal details of, e.g., the Mutex datatype are available for inspection. This is private state by a different name.
"Why was OO popular?" I have a simpler explanation. For many years, the most performant unmanaged language with something even close to resembling a strong or useful type system was primarily an OO language (C++). There were no close competitors. And the fastest managed language was also an OO language (Java). I also deny the proposition that OO is materially harder to learn than other paradigms, supposing you want to learn in those other paradigms how to do the sorts of things that come built-in in OO languages.
This is probably an unpopular opinion but I really can’t wrap my head around the arguments for FP and against OOP.
By the end of the day, regardless of which paradigm you choose, you’re still just defining concepts, and every concept has properties (both externally visible or not) and functions. The problems raised against OOP always seem to be a problem with the average programmer’s lack of understanding of ontology, so then they put structure and function inside concepts where they shouldn’t be. This lack of understanding of ontology is why, in my opinion, there’s equal opportunity for both paradigms to write equally ugly and horrible code.
I mean, I’ve seen people use RxAnything in a modern, expressive programming language that allows both OOP and FP, and still they ended up defining massive view controllers, incomprehensible interface names, extensions that apparently exist but aren’t visible to the programmer from anywhere, property assignments that apparently invoke functions under the hood, etc. If programmers write horrible code with OOP and they still write horrible code with FP, maybe it’s not the paradigm that’s the problem, and maybe it’s the common denominator: the programmer.
> you’re still just defining concepts, and every concept has properties
Sure; but lots of concepts (maybe most) are best expressed as either functions or bare data. OO isn't "bad" - but its massively overused. Lots of programmers (and programming languages) consider it the default abstraction, even when it doesn't make sense. Encapsulating state within a class, with lifecycle methods often makes code more complex, longer (~50% more LoC is common), slower (due to heap thrashing and the inability to vectorize operations), and harder to reason about.
For example, modern javascript has a built-in library for converting binary data to text (either UTF-8 or other formats). This should be a method decodeText(arrayBuf, 'utf-8'). But the API designers instead made a class you need to instantiate first. ( d = new TextDecoder('utf-8'); d.decode(arrBuf) ). This is a strictly worse API, which invokes awful questions like "Is it expensive to instantiate?" "Should I cache it?" "Is there extra hidden state in the text decoder?"
I agree that people can write awful code in any language. I've certainly managed to make messes at one time or another in just about every language I've learned well. But my personal dislike of OO comes from the exhausting, never ending fights I've had trying to convince coworkers to use simple functions and bare structs when appropriate. And to please stop trying to force every program, no matter the language, to look like bloated Java code.
> Sure; but lots of concepts (maybe most) are best expressed as either functions or bare data.
That’s begging the question, isn’t it?
> OO isn't "bad" - but its massively overused. Lots of programmers (and programming languages) consider it the default abstraction, even when it doesn't make sense.
I think that this really drives home my point that the problem is with programmers and their lack of ontology know-how.
Regarding your example on text decoders in JavaScript, you really should be asking the same questions about the functions that you are invoking, too. Just last month there was a huge article here about sscanf taking quadratic time. That could happen to any of the functions that you are invoking.
Again, languages from both paradigms equally allow for bad code to be written. I really don’t think that the debate should be at the paradigm level. We’re better off debating about specific policies in code style.
> Just last month there was a huge article here about sscanf taking quadratic time. That could happen to any of the functions that you are invoking.
I don't really know how that would have been made better or worse by OO. The issue there came from a lack of transparency - its not obvious that sscanf takes time linear with the size of the input string. (FWIW, I didn't know that either).
But both OO and functional approaches can obscure details like that. So it seems kind of tangential.
> We’re better off debating about specific policies in code style.
I don't think good taste can be enforced at a code style level. The problem we have is that all problems can be solved either with OO or FP, or with actors and so on. Its much easier for programmers to learn one style and apply it everywhere, rather than learn all the different ways of expressing something and apply each one only when that approach is appropriate.
Is this what you mean by 'ontology'? When I hear that word I imagine the question of "What is the ontology of data types in our program?" - "We have products, and orders, and users, and ...". Which usually presupposes classes even when they're the wrong tool. Thinking about ontologies is a leading question, and it'll lead you to a different program from classical structured programming's question of "What does your program do". Or FP: "What does your program output, and where does that output come from?" Or dataflow programming: "How does the data move through your system?"
Programming isn't an ontology problem. Its an expression problem - how do you express your program to the computer? I still don't know how to say that in response to an overly OO PR. "Thanks for the change, but this is an inelegant expression of the problem you're solving. Can you please make the data flow primary and obvious in your code, and the ontology secondary?". That doesn't tend to go down well. Even when they're open to it, I get a lot of confused responses. "Huh? Without classes? How would you even do that?"
> Again, languages from both paradigms equally allow for bad code to be written. I really don’t think that the debate should be at the paradigm level. We’re better off debating about specific policies in code style.
This perspective is actually important. When you look at OOP it is a paradigm but not a well defined one. You certainly wouldn't call it a policy.
FP (pure functional programming) however is much closer to a policy in the sense that there isn't so much to argue about. You can see from a piece of code if it is "proper" pure FP or not, whereas for OOP, it is easy to do things that are claimed to be "OOP" by half and claimed to be "improper OOP" by the other half.
And depending on how you define OOP, it is actually completely orthogonal to FP. Just like OOP puts mostly helpful restrictions on how you can model your problem in code, FP puts additional mostly helpful restrictions on it.
287 comments
[ 3.0 ms ] story [ 286 ms ] threadhttps://en.wikipedia.org/wiki/Joe_Armstrong_(programmer)
EDIT: Firefox redirects to https.
Edit: Ah, firefox redirects you to https. I'm using chrome.
Maybe it is, we can’t tell. Maybe the NSA signed it.
Self-signed certificates can’t be trusted unless you have some way of independently verifying then.
"Everything has to be an object": obviously bad, but also not the case in most OO languages. So...bogus objection.
"In an OOPL data type definitions are spread out all over the place": Huh? This one is made up. You can put all your <whatever> in one place if you want to.
"Objects have private state": this is a feature not a bug. Exhibit A: JavaScript.
This can be solved with some knowledge of DDD (for example) which applies to both paradigms.
Personally I prefer when it's all objects. Otherwise you wind up with primitive types that you can't do all the objecty stuff with. Then you wind up with hacks like Java's int vs Integer dichotomy.
Compare this with Python where the internals of the integer type are so hidden that it can do things like seamlessly replace it with a bigint. What it is inside doesn't matter because you only care that it answers to the same messages.
That just sounds like a code organization problem not an argument in favour of OOP.
> but also not the case in most OO languages
The fact that a lot of "OO" languages don't enforce this doesn't change the fact that the general sentiment around object oriented programming as I see it taught and as I see new programmers approach it is that "everything has to be an object."
The problem with OOP is not just OOP languages but also OOP programmers who you have to deal with on a regular basis and have to explain that "this could just be a function, why do I need to instantiate a class to do this basic task?"
> Huh? This one is made up.
Once again, this doesn't matter in light of how people ACTUALLY write code in those languages and what actually is the status quo.
> this is a feature not a bug
Not sure I know enough about javascript to understand what you mean here.
Hiding state inside things is fine when it's done correctly, more often than not in reality when people write object oriented code it is done poorly. I certainly haven't had the same troubles understanding what happens to state when reading someone else's pure functional code vs when I've had to read someone else's object oriented code.
Ouch! Try answering that in some Java Spring or C# MVC code. Then try to answer it in some not class-based Django or Rails code.
Here it is as quoted in:
https://www.johndcook.com/blog/2011/07/19/you-wanted-banana/
>I think the lack of reusability comes in object-oriented languages, not functional languages. Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.
Still, this quote is hilarious:
"Object-oriented programming is an exceptionally bad idea which could only have originated in California." --Edsger Dijkstra
OO says: "State is hard—let's hide it!" (er, sorry, "encapsulate it") FP says: "State is hard—let's isolate it!"
I like F#’s stance on functional-first programming. There are times in which you want to expose the underlying types and there are times you do not. When I recently started a ray tracer implementation, it was a good example of this. The vector, color, point, transform, world, camera, etc. types where all readily implemented by records and discriminated unions which properly isolated but exposed the types. However, for the matrix library, I chose to use F#’s Array.Parallel library and thus a 1D array as the underlying implementation, and this was a perfect use case for using a class. I wanted to hide the implementation of the matrices from the user of the type and encapsulate the internal behavior, only exposing a clean API. Even in that case, the matrix type was immutable because the operations on matrices would simply return new matrices. I think F#’s acceptance of multiple paradigms is really the way to go.
Good object oriented design goes much, much further. To quote Alan Kay, "Doing encapsulation right is a commitment not just to abstraction of state, but to eliminate state oriented metaphors from programming." (emphasis mine)
That's true of much of OOP, but implementation inheritance is the elephant in the room - it can't be modeled or understood using a pure FP model, because the combination of late binding and so-called 'open recursion' requires a dispatch step on all virtual method calls (that would in turn be modeled in FP via a "tying the knot" trick). The resulting semantics are extremely tricky, and most practitioners are aware of the problems with them (see "fragile base class").
Some recent programming languages have abandoned implementation inheritance altogether, e.g. Rust. IOW, they're really more like FP-semantics languages with OOP-like syntactic sugar on top.
In the latter cases it’s not inheritance in the sense that one thing derived from another, but that all the things you’d model that way are derived from broadly compatible base types.
Aside: Rich Hickey’s introduction to Clojure made all of my lisp anxieties vanish when he illustrated the syntactic difference as primarily moving a function call’s open bracket before the function name rather than after. It’s silly in hindsight, but it helped me feel more familiar at once and ready to learn the rest.
Un-aside: I think this kind of exercise would be valuable in translating functional-ish OOP (state is isolated but largely operated on with stateless functions) to its FP syntactic equivalent.
For example in Clojure, in Clojure you could syntactically rewrite (map some-hash some-fn) as a map method on a class instance and it’s just moving some punctuation around.
Another one which is probably not very mind blowing here, but did tickle my brain when I recognized it for what it was: before the great concurrency upheavals over the last decade+ (eventually settling on Promises and async/await), Node had monadic Either/Option types as a core part of its interface (you just destructure them as callback arguments).
None of this is meant to disagree with anything you said of course. Just wanted to add some “if you’re approaching state the same way there’s a representation in your environment” flavor to the discussion.
The basic rewrite rules are almost as simple as the λ-calculus; as I remember the notation, it looks like this:
The expr[replacement/variable] notation implies not only replacement but also α-renaming to avoid variable capture just as in the λ-calculus.You can, for example, define the Boolean values true and false as respectively
and then if you have some unknown Boolean value b you can compute a conditional as follows: Similarly you can define list node prototypes cons and nil where true and false are the Booleans given earlier. Then you can define, for example, a length function assuming you have a suitable interpretation of "1 + expression". And, if not, you can rewrite that to something like one.plus { argument = ... }.result, with a Church-numeral-like construction if you're really enthusiastic.I think the ς-calculus is a lot more ergonomic than the λ-calculus in practice, and I've written things like string-parsing code and vector arithmetic libraries in it, or rather in a programming language I implemented called Bicicleta, which is a thin layer of syntactic sugar on top of the ς-calculus, so you can write things like foo(bar, baz) instead of foo { argument1 = ςx: bar, argument2 = ςy: baz }.result and 3 + 4 instead of 3.'+' { argument = 4 }.result. But it's just syntactic sugar.
I'm still not sure if this was a good idea because I'm really skeptical of whether inheritance at all is a good idea. But if it's a bad idea, it's not because it rules out having a pure FP model or even makes it extremely complicated. It's already common to augment the λ-calculus with things like records, arrays, algebraic data types, even generalized algebraic data types, and Haskell-style typeclasses, any of which add a great deal more complexity than the tiny increment in complexity added by using the ς-calculus as a basis.
https://www.quora.com/What-does-Alan-Kay-think-about-inherit...
However, especially (but maybe not only?) in a dynamic language like smalltalk or ruby, you can simulate implementation inheritance pretty closely with just composition and (some kind of automated/macro'd) delegation if you want to. I'm not sure how/if that changes things at a theoretical/formal level.
Once I heard that, it makes natural sense that Combine became a first class library in Swift, giving the code base ion channels so that messages just aren’t sprayed everywhere with NotificationCenter or requiring you to name each individual “protein/hormone” message in your program.
once? it's Alan Kay's only speech!
I found that it's from this piece, although a brief skim I'm not this piece gives me more guidance about how to do that, but I plan to spend more time with it.
http://worrydream.com/EarlyHistoryOfSmalltalk/
If anyone has other articles to recommend on this concept, please.
Reading it, and learning Pharo, and reading some Smalltalk code, was, for me, one heck of a revelation.
I like this a lot as I run into so many arguments about stateless this or stateless that, and what gets lost is that not contending with state is problematic. Databases are forced to get massive and do all sorts of goofy shit so some people don't have to ever think about state; this goofy shit creates interesting reliability challenges which translates to bad user experiences.
For my future projects, I've decided to build my own application-as-state-database-container thing: http://www.adama-lang.org/docs/why-the-origin-story
Granted, I'm focused on niche board games since they redefine complexity.
Huh? Databases don't do anything "goofy", they implement ACID guarantees.
Which absolutely beats the untold horros of Chtulu proportions that would emerge if programmers had to implement state management at the persistence layer for multiple clients themselves (they'd implement a DB + ACID poorly and in an ad-hoc way, full of holes).
What people seem to forget is that relational databases are not the first technology we had for periststent state. The first versions were custom solutions per app, application-as-state and various ad-hoc such databases with their own formats and techniques, which were such a hellist landscape, devs jumped at relational databases with utter joy...
I think that he meant that functionality that should not is pushed into database. I have definitely seen project full of crazy triggers and sql procedures and so on.
Especially when scale (real or not) is in the picture.
I'm a fan of databases myself, but I am also a fan of document stores as well.
1) different naming convention between members and props
2) dont expose members; use properties from the start for anything public
to assist with two, they introduce the default get/set implementation, like so:
public object Derp { get; set; }
for when you want to expose an interface with the assignment idiom but dont actually have any interesting implementation
I spend a good part of my time training Java developers away from this idea in order to help them make their code more testable and understandable. Queries and Commands are not just pedantic alternate names for Getters and Setters: thinking about objects with respect to what you can ask it to do (Command or Request) and what you can find out from it (Query) significantly improves the code that gets written.
1. Are only conventions. Nobody's forcing you to use them.
2. Simplify refactoring.
3. Are a necessary layer of abstraction any time that 'setting' a value requires updating a related value.
#1 and #2 are only a manner of taste and implementation-specific use cases of tooling built on top of a language. #3, however, is absolutely necessary, to avoid entire categories of business logic errors.
Just imagine if updating a string's value required you to also manually update the string's length. How many string-length related bugs do you think that kind of adventure will result in a typical program?
Arguing against setters is like arguing that
must also be accompanied with The two operations make zero sense to be done separately - doing so is just a minefield of bugs. The purpose of a setter is to combine similar inseparable operations together.A trivial pass-through setter obviously provides ~zero value, but it also carries with it ~zero cost. Any compiler worth its salt will optimize its invocation away.
You should have a constructor/factory for strings that will form/instantiate the string in a valid state, with its value matching its length. That's not the same thing as a setter that mutates the string in-place.
The real question with all features is "Does it make a given task easier, better, or possible Y/N?" Said question is incredibly vague by design and should differ by situation.
This is true, and it’s the primary reason I’m not finding some way to work in Haskell. But it’s also kind of a cop out.
The issue isn’t just that state exists and will do well beyond our extinction. It’s not even just about how you manipulate state or how many restrictions you put on mutability. It’s also about the visibility of state and stateful processes, how that’s signaled, and the conventions that fall out.
This isn’t something I fully understood until I worked with Clojure. Immutable-by-default was a fantastic constraint. But more valuable to me was that where state is accessed or changed, you can tell immediately.
Just having that gives you several insights:
- This code is more likely to be impacted by outside behavior
- This code has additional concurrency requirements
- This code may be unnecessarily complex
- This code may be hard to understand a week or more later
- This code is documenting its implicit dependencies
Since I’ve moved on from Clojure I’ve mostly worked in TypeScript, and I’ve done my best to apply the same principles.
In some ways it’s a loss: you can’t signal stateful access with @ or ! or *.
But that’s all convention. In other ways it’s a huge win: if you’re trying to write functional code in TS, you eventually end up with the idiom that async/await and Promise types are generally signals of state. And the type system calls that out much more reliably.
All of that said, you do gotta stick the state somewhere. But where and how you do is the difference between eternal pain and eternal much-less-pain. And coupling state with functions on objects is definitely more likely to produce more eternal pain.
Ok, now I'm curious about how StateT fails it.
The state is just data structures. You don't need to stick it anywhere aside from some namespace.
You can then have functions operating on those data structures and be fine.
For protection/privacy just make sure access to the data structure instances is not global.
HN's "past" link: https://hn.algolia.com/?query=Why%20OO%20Sucks%20by%20Joe%20...
"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."
no, it won't make coding any better
yes, we'll keep hearing about it forever
Why OO Sucks by Joe Armstrong (2000) - https://news.ycombinator.com/item?id=19715191 - April 2019 (380 comments)
Why OO Sucks - https://news.ycombinator.com/item?id=9481369 - May 2015 (16 comments)
Joe Armstrong: Why OO Sucks - https://news.ycombinator.com/item?id=4245737 - July 2012 (256 comments)
Why OO Sucks - https://news.ycombinator.com/item?id=474919 - Feb 2009 (114 comments)
In case the error was unintentional, _ad nauseam_.
[1]: https://en.wikipedia.org/wiki/Ad_nauseam
Then that Gang-of-Four "Design Patterns" book became popular and really screwed things up. I swear I didn't see factories-making-factories-making-factories until that thing was published, then I couldn't go a day without encountering yet another SingletonFactoryWorkerVisitor or whatever was cool that week, ugh.
I'm joking, of course. Except about Gang-of-four. And rewriting your code.
No? That's a pretty good description of the sort of metaprogramming that Lisp gives you, and Lispers are really enthusiastic about that kind of thing. People will dispute the parallels, but the real difference comes down to:
- non-Lisp languages being significantly less powerful
- Lisp's veneer of respectability
... which shouldn't be all that odd, considering the person who gave us "design patterns" was Dick Gabriel, a Lisper.
At least the "SingletonFactoryWorkerVisitor" programmers use a nomenclature that reflects the ontology the object is supposed to fit into.
The Gang-of-Four-driven stuff I saw in C++ and Visual Basic (late 90s and early 00s) still gives me the shivers. Like, "Hey, factories are cool, let's make a bunch of them for no reason because we might need the flexibility someday." Sigh.
Compile and run time code generation seem to be one of those things. Having a mostly pure language (even when there aren't guarantees) makes them much saner.
1 - Pre-compile time generation, by its turn seems to never work very well. And if there is developer interaction between the code generation and compiling, than it's always a disaster.
So `amb` is, in object-oriented parlance, a state stream factory factory factory. Upon being passed a `term list`, it returns a factory, which, upon being passed a `term`, returns a factory, which, upon being passed a `state`, returns a `stream`. (I only traced the `null` path in this comment, but the `disj` path also returns a stream, specifically a `state stream`—OCaml's static type checking guarantees that both return paths return compatible types.)
You might think that isn't the way it's actually used, but my example code uses it in precisely that way:
Here `amb` is being invoked with just a `term list`. (OCaml separates list items with semicolons, so that commas always make tuples.)So I think factory factory factories are very useful, but also I think they're really hard to keep under mental control without a functional language with a strong static type system. How on earth would you translate those four lines of code above into Java? With four inner classes, maybe, nested eight levels deep with {{}}?
If you're interested, I was translating μKanren, a Prolog-like logic-programming language originally written in 39 lines of Scheme, into OCaml: http://canonical.org/~kragen/sw/dev3/mukanren.ml
Without the mutable state it's not a factory, it's just a function, and there's no confusion and no problem.
Although the GoF Abstract Factory pattern doesn't rule out having mutable state in your factory object, that is neither necessary for its purpose nor common practice when applying it in OO programming languages. The example in GoF is MotifWidgetFactory (for shitty Unix GUIs) vs. PMWidgetFactory (for shitty OS/2 GUIs), and in fact GoF says, "An application typically needs only one instance of a ConcreteFactory per product family," suggesting that they were thinking of cases where the factories don't even have immutable state such as the `terms` argument to `amb`.
They do explore a "prototype-based" factory that is stateful—but there the statefulness is just used as a way to configure the factory at startup, by building up a "product catalog" in it. Creating products doesn't modify the factory's state, and you could configure the "product catalog" just as easily by passing it in as an argument when the factory is instantiated; it's just that, in Smalltalk, that kind of thing is normally represented by segments of executable code that invoke a long sequence of side-effecting methods, rather than by data structures.
(You can also represent that kind of thing invoking a long sequence of pure functions, as in the "fluent interface" pattern that became popular with JQuery, but that isn't the Smalltalk way to do things, especially in 01994.)
The GoF examples other than the prototype factory are WidgetFactory and MazeFactory; in InterViews, WidgetKit, DialogKit, and LayoutKit; and in ET++, WindowSystem. Kerievsky's examples in Refactoring to Patterns (where he points out that people constantly conflate the various "factory" patterns, and that the lines between them are somewhat vague) are HTML parser AST node creation, ORM attribute descriptor creation, java.util.SynchronizedCollection and UnmodifiableCollection, and an OutputBuilder interface that allowed him to create either DOM nodes or XML output from the same unit test code. In all of these cases except the OutputBuilder, the factory objects are stateless.
So, by your definition that a factory is what you get if you take a function and add mutability, we find that 6 out of 7 of the GoF examples of "factories", and 3 out of 4 of Kerievsky's examples of "factories", are actually "functions" rather than "factories". I think this clearly shows that the categorization you propose does not coincide with the scriptural explanation of what "factory" means.
⁂
It's true that, in the case where there's no state, you can do this with just a pointer to a function. But pointers to functions don't exist at all in languages like Java, and aren't first-class values in languages like Pascal, where they are subject to various restrictions that keep you from using them like this.
Even in languages like C, which have first-class function pointers but not closures (except as a GCC extension subject to Pascal-like restrictions), you can't create new function pointers at run-time. So in C, or C++, you can replace a singleton factory like the ones GoF considers usual with a function pointer, or maybe a record of function pointers. But you can't write a factory factory that way, much less a factory factory factory.
So, while you're right that in a sense an immutable factory "is just" an indirectly-invoked function—that's the main point of my comment—if you want to do that in a language like Smalltalk or C++ (at least in 01994, when they wrote the book) or Java (at least in 02000), the Abstract Factory pattern in GoF tells you how to get it working.
An object by definition encapsulates mutable state. Even if a particular object happens to not contain any mutable state, there's no way to observe that from outside, by design.
If your language forces you to use an object to represent an (immutable) function value, all I can say is don't use such a shitty language.
If I read you correctly, your definition of "object" excludes each of the different concepts that C++, D, Python, and Abadí and Cardelli's object-calculus call "objects", but includes what Scheme, OCaml, and Kotlin call "functions"? You are of course welcome to use that definition, but it might improve communication to clarify that you're using the same word that other people use but with a completely different meaning.
> don't use such a shitty language.
There's a plausible reading of the GoF book and the whole "OO patterns" movement that it's largely about how to make do with shitty languages, though I'd nominate SICP as being better at that. Often languages that are shitty on one axis have compensating advantages on other axes—for example, although you can script Cocoa with cffi in Python, which is less shitty than Objective-C for scripting in general, you waste a lot more time debugging segfaults; despite the admitted aesthetic advantages of 68000 assembly with respect to amd64 assembly, the latter performs noticeably better on this laptop; and, while I think I've convincingly shown that a factory factory factory is dramatically more readable in OCaml than in Java, you're probably going to have a much easier time getting your remote dictionary server performance to scale to 16 cores in Java than in OCaml. If you have enough RAM, anyway.
That's not my intention; I'm trying to match the definition of an "object" in common programming languages. As far as I can see an object (instance) is essentially a bundle of method handles associated with an opaque (possibly empty) bundle of (possibly) mutable state. (In some languages the bundle might also include some visible state, but that doesn't change the essence of the thing, since that can be emulated with method handles - indeed Kotlin does exactly this). In Python's case the opacity of that state is a convention rather than a strict rule, but I think the general consensus would be that it should generally treated that way, and also that Python's objects are somewhat less object-ey than those of other languages. Certainly I'd say this definition is a close fit for C++ and Kotlin; what are you seeing as different there?
(Hypothetically in CPython maybe you could disassemble the bytecode of a method to see if it tries to mutate the object or some global variable, but even if this is possible, you can also use cffi to change the value of 3 in CPython. I regard both of these as breaking the semantics of Python per se.)
So, I was wrong about Python. And, on further reflection, I was wrong about C++ as well, because even if you provide a `const`-argument override for a function, which is a thing you can do to observe whether an object is `const` or not from the perspective of your caller:
there's not even a compiler warning if your caller fails to do so as well: D's `immutable` is externally observable in the same way as `const` in D or in C++, but rigorously so in the sense we want here: invoking a function whose parameter is `immutable` with a mutable object is a compile-time error.Also, D's `immutable` is transitive, unlike C++'s `const`, but like, for example, E's DeepFrozen.
In the simple object calculus, everything is an object and there is no mutability at all. You could reasonably object (heh) that, although it's intended to capture object-oriented programming, and Abadí and Cardelli's book is called A Theory of Objects, it's not widely used, so opinions may differ as to whether they succeeded. https://news.ycombinator.com/item?id=26588642 goes into more detail there.
So your definition of "object" only excludes each of the different concepts that D and Abadí and Cardelli's object-calculus call "objects" (and I guess I should add E), not Python's or C++'s as well.
As for Kotlin, my claim about Kotlin was not about Kotlin "objects" but about Kotlin "functions", which are closures that can include access to mutable state, just as in Scheme and OCaml. I don't know Kotlin well enough to know whether your definition of "object" fits its definition of "object". It's an interesting question. Maybe it does! Kotlin's data classes, for example, can contain mutable attributes, and there really is no (non-reflective) way to observe them from another class — for example, for a method to refuse to accept an argument if it happens to be mutable in this way. Kotlin/Native evidently has a form of transitive and externally observable immutability, but I don't know much about it.
Your revision of the definition to include a bundle of method handles does seem to neatly exclude what are called "functions" in Scheme, OCaml, and Kotlin. I think it might also exclude objects in E, but since E is dynamically typed, that's sort of an implementation detail, and anyway E doesn't figure prominently in the consensus definition of the term "objec...
And so it goes with the others, many of which I use extensively in my own work, but at most there's a comment at the top saying, for example "this business rules structure is in the chain-of-responsibility pattern".
That is the material use of patterns, they are not abstract frameworks. Naming things makes a difference: encouraging a taxonomy of purpose, rather than of form, goes a long way to re-orienting a programmer towards problem-solving with concretions rather than abstractions. As a consequence, Design Patterns, along with Refactoring and PoEAA, continues to serve as one my most-thumbed indexes of solution ideas.
Both existed for a long time before, but did not had names that would clearly instantly made them recognizable as such.
You just need to not pay any attention to design and start coding right away. Then you'll rewrite your FP code even more than you did for your OOP code, and still have an easier time overall.
In all seriousness, I've found that a very nice way to develop FP code. It always looks good at the end anyway, and there's no risk of getting into astronaut mode.
Meaning, due to our brains being differently wired some forms of programming might be easier to understand and use for some people while other for others
But we definitely know how to make things worse for just about everyone. Use brainfuck. Or Piet. Or a straight-up turing machine.
And if we can make things (pretty much) objectively worse, then it's very likely we can make things objectively better. And there's not much reason to assume we've already reached some kind of end-game, where we're at the tip of the spear for language design, or even the core fundamental models of design.
I mean shit, we're only like 60 years into the subject. The legendary programmers of lore still walk the earth.
It might take some rewiring, but I for one believe there is a much better world for us to program in, than C, Java and C#.
I think much more likely than different "brain wiring" is universal brain damage, from working, breathing, living, with the languages we know, love and despise today. Brain damage might be harder to fix however; in which case we're dealing with language advances one funeral at a time.
Many of the theoretical advantages of OO in low level programming low level don't apply to modern, high level languages, however the encapsulation, convention, and principles (SOLID) of modern OO development are unassailable by current alternatives as the best way to throw that number of software engineers at a single project.
What makes enterprises work are:
- clear responsibilities
- clear requirements
- clear interfaces
- clear ownership
- good tests
- good communication
More on the point of the article, it's sort of fun to think about the fact that "OO Sucks" is sort of ironic given Alan Kay's initial description of OO was closer to actors than what we think of OO today (he acknowledges this at a later time).
As Kay puts it:
I'm sorry that I long ago coined the term "objects" for this topic because it gets many people to focus on the lesser idea. The big idea is "messaging" [..] The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be. [0][1]
This is very much in the spirit of Armstrong's quote in the article:
> Since functions and data structures are completely different types of animal it is fundamentally incorrect to lock them up in the same cage.
Armstrong talked a lot about how shared mutable state was wrong on a fundamental level - it "breaks reality(/causality)" and that sort of thing. Again, sort of fun to think about the fact that the core ideas with actors seemed to have an origin in an early focus on asynchronous 'cell'-like computers, like the JOHNNIAC in 1953[2], even though the foundation of the model wasn't named or formalized until the 70s.
"Its designers began with the hope of stretching the mean free time between failures and increasing the overall reliability by a factor of ten". Systems like JOHNIAC where IAS machines - asynchronous CPUs. These worked through causality, not synchronization.
In Armstrong's thesis[3] 'Making reliable distributed systems in the presence of software errors' he references papers like [4] 'Why do computers stop and what can be done about it? Technical Report 85.7, Tandem Computers, 1985.', which talks again about isolated processes and transactions as a foundation to reliable computing - over 30 years later.
This whole area is so deeply fascinating with a century of repeating, refining ideas, and I found that just by reading what I could from Armstrong I had a sort of rough guide through this area. There are these cool ties to early ideas about AI, and I guess a lot of people though that languages should model life, and later Kay talks about this as well, and funny enough AWS now has "cell based architecture" - their discipline built around isolation and fault tolerance. A lot of this is sort of just random connections from jumping from paper to paper - but I just found it all really cool.
Reading this thread, it almost seems like people don't know who Joe Armstrong is? Or at least they've missed a lot of the point. This isn't an "X vs Y" from some rando, he built Erlang. It's also not about functional programming.
I highly recommend reading what he has to say, and watching his talks.
[0] http://wiki.c2.com/?AlanKayOnMessaging
[1] https://computinged.wordpress.com/2010/09/11/moti-asks-objec...
[2] https://www.rand.org/content/dam/rand/pubs/research_memorand...
[3] https://www.cs.otago.ac.nz/coursework/cosc461/armstrong_thes...
[4] rramadass ↗ In case you don't already know about it; Handbook of Neuroevolution Through Erlang by Gene Sher is a great demonstration of the approved way of using Armstrong's ideas. staticassertion ↗ Thank you! Unfortunately the part of my career where I had a lot of time to dive into this sort of thing is behind me (and hopefully ahead of me too), but I'll add that to my backlog.
* immutability and absence of state is preferable to mutation and statefulness
* Option/Maybe types (or "nullable types" which are a shoddy implementation of the same thing) are better than null
* Either/Result types are better than exceptions
* making things implement map, filter etc and sending in a function that describes what you want to do is better than manually eg looping through lists etc
etc etc etc
Anyone who doubts how endorsed this is, just read what Brian Goetz and Josh Bloch have to say about how to code in Java.
Just imagine if these languages had been implemented with these ideas in mind from scratch instead of the current situation of trying to adopt and retrofit this style when the core libraries fundamentally don't support it.
The current trend of "OOP" languages is basically inexorably heading towards FP and abandoning the old school "OOP" style. Eventually they will only be nominally "OOP", mostly in order to please people who have irrational attachments to labels like that, but be way more FP in nature and in all but name.
For what it's worth, people shouldn't be irrationally attached to the "FP" label either. Labels aren't important - what matters is the code, how easy or hard it is to reason about it, how well it avoids entire categories of defects from even being possible etc etc.
https://proandroiddev.com/kotlin-avoids-entire-categories-of...
Maybe not always. I suspect, based on Kay's writings, that Smalltalk's "everything is an object" thing was more about experimentation. They were trying to push one simple idea as far as possible, in order to see just how far they could push it. It turns out you can go pretty far. That doesn't necessarily mean you need to.
That said, the most popular language that shies away from "everything is an object" - Java - does so in a way that doesn't work well. Primitives are not objects, sure, but primitives then integrate poorly with the rest of the language (especially since Java 5), and more complex data structures must be objects, even if you don't want them to be.
Ironically, my favorite language for showing the nice things you can get by relaxing the "everything is an object" ethos, F#, actually does make everything into an object. (It has to, because .NET.) But it doesn't force you to think about them as if they were objects when you don't want to. So the mental space you live in when you're using the language feels primarily functional.
But sometimes objects are useful. The key distinction here - and the thing that Armstrong seems to miss in this essay - is that methods aren't just functions that have been glued to some data. Technically I suppose they are - that's certainly how it works when you roll your own OOP in a language like C - but it turns out the whole is more than the sum of its parts. Because you get a really useful thing that isn't so convenient to do when you keep your functions and your data separate: dynamic dispatch.
Dynamic dispatch - not encapsulation - is the killer feature of objects. Late function binding allows you to operate over heterogeneous inputs where the only thing you want to enforce is that they obey a protocol, without having to do mess of explicit conditional branching or having to have all the details pinned down at compile time. Functional languages without any OOP facilities run into convenience problems here. Typeclasses cover some of the same use cases, as well as others that objects and interfaces don't handle very well at all, but they're statically bound, so they can be somewhat less flexible. It's often stated that design patterns are just a way of making up for a missing feature in your language. Well, the command pattern is what you do when your language doesn't support OOP.
(For another example of where Armstrong was clearly operating under some misapprehensions when he wrote this, look at the truly bizarre statement he makes in the last paragraph of section 3.)
With typeclasses, polymorphic behavior is driven strictly by the type, and one must follow that thought to it's conclusion to see the difference. Sometimes it's more useful, sometimes not.
You can do that in typeclass-based languages as well, e.g. in Haskell:
The `showAll` function takes a list of objects that implement the `Show` typeclass. The objects themselves can be of different types. The syntax is slightly less ergonomic, but the runtime effect is much as you would expect from an OOP language—a pointer to the typeclass dictionary ("vtbl") is paired with each object, and the address of the appropriate `show` method is looked up at runtime for each element of the list.I’d like to hear some things from people who only started programming in languages like that and know no other way, but have maybe learned C or something.
I tend to miss lambdas more than classes in c.
I'm not big on large class hierarchies, so classes tend to be more about conceptual organization to me. And throwing a bunch of related function pointers into a struct and passing a pointer to the struct itself almost kinda looks like a class if you squint hard enough. It keeps everything organized.
Packing everything I need into a custom struct, coercing it to a void pointer, and passing it somewhere to emulate a closure, on the other hand, feels dirty.
So, yes there are popular non-OO languages today. No, they probably don't dominate in overall interest or use, at least outside certain domains. But people are even putting Javascript on microcontrollers these days so it won't be long before it's OO everywhere. Hell, OO (via Java) has already been to Mars.
https://www.tiobe.com/tiobe-index/
funny enough I worked at a place a year ago who’s main language was VB6 and they were terrified of objects. Mostly exposed to it on the web side with python and php written by two new developers
Oddly some of their best code was some VB6 using OOP that someone had snuck in sometime years ago.
Javascript is object oriented but has it's own definition of "object" that differs from every other language, so people try to abstract it away and write most of their code in some other paradigm.
Aristotle wrote about change and thought about change in four ways. The material from where object came from. The form or template or shape of the change. The efficiency or agent that caused the change. The purpose or reason or final cause of why the change was made.
So instead of classes and methods. If we followed this paradigm, we could be now discussing forms and changes.
Of course business folks would think this is all silly. They speak the language of accounting which records transaction entries in a journal. Each month the books are closed and the transactions are summarized as the closing balance. Instead of classes, the talk about general ledger codes. Instead of methods, they talk about entries and reverse entries to correct an error. Instead of state, they talk about auditing the entries to verify the balance.
[1] https://davesgarden.com/guides/articles/view/2051
The problem with OOP in 2021 is that it tends to be the default, when arguably starting from safety and working back toward performance would be a better approach.
Strings are a good illustration. Instead of an abstract polymorphic String type, Haskell provided a concrete String type as an ADT. This proved too inflexible, which is why we have Text, lazy Text, ShortText, etc. Compare to NSString which also has multiple representations but hides them behind a single polymorphic interface.
Polymorphism is front an central to everything in Haskell, which is why your comment sound off to me.
ADTs are not an intrinsic part of FP, as not all FP languages even have them.
I'd also question whether ubiquitous polymorphism is overall a good thing in a language, or whether it's misguided complexity. In most OOP languages, any public method can be polymorphic, but a polymorphic function is inherently less predictable than one dispatches off a single type.
"Everything has to be an object" is only true in some languages, and even then it's a questionable claim. If I have a Java class that has public data members only, how is that materially different from a data structure?
"Data type definitions are spread out all over the place" Again, a questionable assertion. It's plenty easy to define all your data types in one package or header file in Java or C++. Some folks choose not to organize it that way, but that's a different and substantially weaker objection.
"Objects have private state" Unless you want all parts of your system to be able to depend on the internal state of all other parts of your system, some kind of state access control is going to be necessary. I'm not aware of any system, OOP or not, where the internal details of, e.g., the Mutex datatype are available for inspection. This is private state by a different name.
"Why was OO popular?" I have a simpler explanation. For many years, the most performant unmanaged language with something even close to resembling a strong or useful type system was primarily an OO language (C++). There were no close competitors. And the fastest managed language was also an OO language (Java). I also deny the proposition that OO is materially harder to learn than other paradigms, supposing you want to learn in those other paradigms how to do the sorts of things that come built-in in OO languages.
By the end of the day, regardless of which paradigm you choose, you’re still just defining concepts, and every concept has properties (both externally visible or not) and functions. The problems raised against OOP always seem to be a problem with the average programmer’s lack of understanding of ontology, so then they put structure and function inside concepts where they shouldn’t be. This lack of understanding of ontology is why, in my opinion, there’s equal opportunity for both paradigms to write equally ugly and horrible code.
I mean, I’ve seen people use RxAnything in a modern, expressive programming language that allows both OOP and FP, and still they ended up defining massive view controllers, incomprehensible interface names, extensions that apparently exist but aren’t visible to the programmer from anywhere, property assignments that apparently invoke functions under the hood, etc. If programmers write horrible code with OOP and they still write horrible code with FP, maybe it’s not the paradigm that’s the problem, and maybe it’s the common denominator: the programmer.
the benefit of FP as a practice is more modular functionality. imperative code isnt always tangled, but it often is. OOP is for more modular data
Sure; but lots of concepts (maybe most) are best expressed as either functions or bare data. OO isn't "bad" - but its massively overused. Lots of programmers (and programming languages) consider it the default abstraction, even when it doesn't make sense. Encapsulating state within a class, with lifecycle methods often makes code more complex, longer (~50% more LoC is common), slower (due to heap thrashing and the inability to vectorize operations), and harder to reason about.
For example, modern javascript has a built-in library for converting binary data to text (either UTF-8 or other formats). This should be a method decodeText(arrayBuf, 'utf-8'). But the API designers instead made a class you need to instantiate first. ( d = new TextDecoder('utf-8'); d.decode(arrBuf) ). This is a strictly worse API, which invokes awful questions like "Is it expensive to instantiate?" "Should I cache it?" "Is there extra hidden state in the text decoder?"
I agree that people can write awful code in any language. I've certainly managed to make messes at one time or another in just about every language I've learned well. But my personal dislike of OO comes from the exhausting, never ending fights I've had trying to convince coworkers to use simple functions and bare structs when appropriate. And to please stop trying to force every program, no matter the language, to look like bloated Java code.
That’s begging the question, isn’t it?
> OO isn't "bad" - but its massively overused. Lots of programmers (and programming languages) consider it the default abstraction, even when it doesn't make sense.
I think that this really drives home my point that the problem is with programmers and their lack of ontology know-how.
Regarding your example on text decoders in JavaScript, you really should be asking the same questions about the functions that you are invoking, too. Just last month there was a huge article here about sscanf taking quadratic time. That could happen to any of the functions that you are invoking.
Again, languages from both paradigms equally allow for bad code to be written. I really don’t think that the debate should be at the paradigm level. We’re better off debating about specific policies in code style.
I don't really know how that would have been made better or worse by OO. The issue there came from a lack of transparency - its not obvious that sscanf takes time linear with the size of the input string. (FWIW, I didn't know that either).
But both OO and functional approaches can obscure details like that. So it seems kind of tangential.
> We’re better off debating about specific policies in code style.
I don't think good taste can be enforced at a code style level. The problem we have is that all problems can be solved either with OO or FP, or with actors and so on. Its much easier for programmers to learn one style and apply it everywhere, rather than learn all the different ways of expressing something and apply each one only when that approach is appropriate.
Is this what you mean by 'ontology'? When I hear that word I imagine the question of "What is the ontology of data types in our program?" - "We have products, and orders, and users, and ...". Which usually presupposes classes even when they're the wrong tool. Thinking about ontologies is a leading question, and it'll lead you to a different program from classical structured programming's question of "What does your program do". Or FP: "What does your program output, and where does that output come from?" Or dataflow programming: "How does the data move through your system?"
Programming isn't an ontology problem. Its an expression problem - how do you express your program to the computer? I still don't know how to say that in response to an overly OO PR. "Thanks for the change, but this is an inelegant expression of the problem you're solving. Can you please make the data flow primary and obvious in your code, and the ontology secondary?". That doesn't tend to go down well. Even when they're open to it, I get a lot of confused responses. "Huh? Without classes? How would you even do that?"
This perspective is actually important. When you look at OOP it is a paradigm but not a well defined one. You certainly wouldn't call it a policy.
FP (pure functional programming) however is much closer to a policy in the sense that there isn't so much to argue about. You can see from a piece of code if it is "proper" pure FP or not, whereas for OOP, it is easy to do things that are claimed to be "OOP" by half and claimed to be "improper OOP" by the other half.
And depending on how you define OOP, it is actually completely orthogonal to FP. Just like OOP puts mostly helpful restrictions on how you can model your problem in code, FP puts additional mostly helpful restrictions on it.