This is just a cool, affirming day for me with several recent articles praising dynamic languages, to pointing out flaws with OO in practice.
I'm fairly pragmatic when it comes to OO, FP etc.. I happen to like modern JS, I like FP for most workflows, and will use classes where it makes sense. By not needing to create DI systems and other "enterprise" patterns early on, I'm able to create simpler solutions... in most cases FP/Procedural hybrids make more sense in terms of workflow/state ... for state that is tethered to UI context, but no necessity to persist/reload will use more class oriented approach (React + Redux for example).
Once you're past the initial boilerplate and concepts, adding features adds far less complexity this way than with more traditional OO approaches.
Interesting, I reached more or less the same conclusions about 10 years ago, after about 20 years programming in C++.
I think with C++ I had also reached the point where I realized it was not just difficult to scale (I had worked on ginormous codebases) it was also difficult to 'control'... Adding new people on the project could sometime mean disasters that would go unseen for a while before exploding in your face...
Newbie programmer decides it's a cool idea to go and 'tweak' the String class - or any other base class- or other equivalent 'good idea' that sometime lead to huge amount of effort to understand the weird ripple effects afterward, and then 'fix' so it's done 'properly'.
There was also the 'design for design' problem where a 1/2 page feature that was ever going to be used ONCE was designed with an imbroglio of 5 or 6 classes just to match some person's idea of a 'design pattern'.
I've now 'reverted' to mostly 'procedural' C; however, quite a lot of the 'good' concepts of C++ for encapsulation can be done in C without all the faffing around, and it's a LOT harder to fsck up and create ripple effects; you can 'layer' modules instead of 'inheriting' with the equivalent compartmentalization you'd want from C++, without the 'dangers' of inheritance.
Sure, it lacks syntaxic sugar, and sure, there's still stuff I miss; but it does scale pretty well, and the footprint difference is fantastic.
I think you need to take a step back, because you are analysing your problems at the wrong abstraction level. It sounds like you had an engineering problem, not a programming language one.
Changing the programming language won't prevent newbie programmers from wreaking havoc in your code base, nor improve the software design skills of your colleagues.
On the other hand, I do agree that programming languages have a "culture" which defines the idioms that are accepted, patterns, attitudes towards various practices. My feeling is that hyper-oop programmers have long moved on to Java and .NET.
C has a culture of keeping things simple, because of its long history and limited abstraction possibilities. It's also the favorite refuge of people that hate OOP, so no surprise you won't see over-engineered OOP hierarchies.
The newbie programer effect is part of the lacking Integration of Zoning in a Codebase. Thus its a IDE problem.
You cant go and declare some basic tool classes only editable by a expert/architect or a newb under "SignOff" Supervision.
There is also no real metric on expertise, and a way to prevent non-experts to go banana on something beyond the scope of there fields. You do not want a assembler coder going first time OO and in reverse. What actually would help is a sort of guild - where one has to accomplish certain feats first, before beeing allowed to level up..
I agree that in an ideal world, it would work like that; however in most companies, 'programmers' are hired, rarely as 'juniors' and sometime are moved across the company to a new team, who then realize they are... err ... 'junior' at best.
Then there is the minefield of how to break the news to someone that they can't be trusted 'just yet', with the discrimination minefield that implies etc.
And in many companies, you can't even go an tell a colleague that their change is in fact, crap, and has to be redone properly. Never been in a company where that didn't create huge waves, regardless of the level of proof you can attach to it.
I hear in some companies (google?) it's expected, but it's an exception, in my experience...
Anyone who works on a large system written in any paradigm can see the same "disaster" -- at least so far[1]. What so far differentiates paradigms that claim they are immune to those problems (or other problems with similar severity, some of them perhaps unknown as of yet) is that they have never been put to the test on projects of the same magnitude (i.e., same codebase size, same team size, same project lifespan[2]), let alone in enough problem domains, so they haven't had a chance to encounter "disaster inducing scenarios", and therefore haven't reported disasters yet. What we have now is paradigms that we know to be problematic -- but also effective to some degree (large or small, depending on your perspective) -- and paradigms that we don't know enough about: they could turn out to be more effective, just as effective or even less effective (or they could be any of the three depending on the problem domain).
How can we know if we should switch to a different paradigm? Empirical data. So please, academic community and the software industry: go out and collect that data! Theoretical (let along religious) arguments are perhaps a valid starting point, but they are ultimately unhelpful in making a decision. In fact, it has been mathematically proven that they can contribute little to the discussion: the theoretical difficulty in constructing a correct program is the same regardless of the abstractions used[3]; only cognitive effects -- which can only be studied empirically -- could provide arguments in favor of a certain paradigm making it easier for humans to write correct code.
[1]: As someone who worked on software written in procedural code before the popularity of OOP, it wasn't any better (actually, it was worse). For contemporary examples, see Toyota's car software.
[2]: Those are, of course, proxies for "very complex requirements, both functional and structural", but the point stands.
[3]: The cost of verifying/constructing a correct program is at least linear in the size of the program's state-space (or, more precisely, its Kripke structure, which also includes transitions) regardless of representation in code or abstractions used to make it more succinct. E.g., "Kripke structures that admit a succinct representation are not simpler for model checking purposes
than arbitrary Kripke structures" (http://www.lsv.ens-cachan.fr/Publis/PAPERS/PDF/Sch-aiml02.pd...), and model checking can be reduced to any other verification/construction methodology.
That quote is often cited, but it stymies the discussion.
First of all because there are counterexamples. E.g. I think SQL is generally well-liked for its domain and widely used.
Secondly, even if we complain about languages that are used, we can still distill and compare pros and cons. Speaking of C++ specifically - once Rust is in wider use, people will probably complain about it too. But we can describe in what regards Rust is safer objectively.
I don't like SQL. I think there could be a worthy successor to it. Something functional, typed, Haskell-like. Something that can incorporate other data processing paradigms, such as ETL, stream processing, map reduce and so on.
After reading enough of these articles, one comes to a disturbing conclusion: a lot of software engineering practices and a lot of "common sense" knowledge are based on anecdotes, blog posts, famous engineers' declarations or arguments on the internet.
It shouldn't be very difficult to prove that a particular programming paradigm is better along some axis than another.
I think it's great that Brian is happy and more productive, but that has no meaning to me if I want to make an informed decision about FP.
> It shouldn't be very difficult to prove that a particular programming paradigm is better along some axis than another
I think it would be one of the hardest things to ever prove scientifically actually. Basically the only valid method I'd accept is having a large group of people who are non-developers, then train half of them in one paradigm and the other in another. Then have both groups solve the same set of problems.
The problem with this experiment is that in order to get useful results is that you need a good group of people that are representative of programmers (i.e. people that don't know programming, but can and are willing/able to learn). Say you can find 1000 mechanical engineering students for example. Great. Next they need to be trained. How long does that take? A year or two? Finally they need to solve the problems, which should be large problems. Next the solutions to the problems should be carefully evaluated. I'd say the experiment if done in academia would be ridiculoulsy expensive and time-consuiming.
Numerous studies such as on static vs dynamic typing have used tiny programming experiments done by a small groups of students doing trivial exercises over the course of a day. I'd say they give little or no insight in how experienced developers handle large problems.
It's very hard to do it as a natural experiment too. A large company could try giving the same task to a team of OO developers, as it does to a team of FP developers. This has happened and the result is usually that the FP developers do it in half the time with half the bugs and twice the enthusiasm. However -- can we know that the people who ended up being FP developers weren't just better developers than the OO devs? I'd wager that "enthusiasts" are more commonly drawn to FP today, so the base of FP developers is a group that is on average "better" than the OO devs. So while I don't doubt that FP is "better" in most scenarios, I also belive the FP group would have done a better job with the OO language as well.
> Basically the only valid method I'd accept is having a large group of people who are non-developers, then train half of them in one paradigm and the other in another. Then have both groups solve the same set of problems.
I'd settle for two competitors working in the same domain producing similar products using different paradigms, reporting significantly different costs throughout the project's lifetime (including maintenance). Or even a single company reporting a vast decline in costs after switching paradigms, provided that the project is big enough and lasts long enough (five years at a minimum, a decade is preferable). Of course, the more domains and more cases per domain the better. This isn't theoretical. We had numerous case studies of this sort in the late '90s-early '00s showing a very big cost reduction when moving from C/C++ to Java, in projects ranging from enterprise software to defense.
> Unfortunately they are all likely to support different conclusions.
... which means that the case is not so clear cut, which may suggest that the increased effectiveness of some methods is not universal but restricted to specific domains or even specific team compositions.
The problem with two competitors is that you still can't be sure that the firm that used FP either had "better" developers who would have done the project better at the other firm too. I have met almost no bad developers who do and enjoy FP, and I have met tons of bad OO developers. Actually, even OO developers that just know of the concept of FP are way in my experience way better OO devs than those who don't.
A company switching paradigms is a better test, since the same developers are involved, and presumably they would be less experienced in the new paradigm than the old. If they still produce better software that is a good indication that it was a good switch. Still, there may be a number of companies that did this and failed because they couldn't reach profitability with a new code base, or couldn't fill vacancies and so on. Like I said, it's very hard.
> The problem with two competitors is that you still can't be sure that the firm that used FP either had "better" developers who would have done the project better at the other firm too.
Yes, but over enough such cases, you can average this out. Also, better developers are more expensive, so that is factored into the cost.
> I think it would be one of the hardest things to ever prove scientifically actually. Basically the only valid method I'd accept is having a large group of people who are non-developers, then train half of them in one paradigm and the other in another. Then have both groups solve the same set of problems.
... and have them maintain the resulting software for years and years. Otherwise you're mostly "just" looking at learning curve, I should think.
In all fairness I think that would actually be a tougher situation, all things considered :).
It's a sort of weird testament to the efficiency (not efficacy) or inscrutability of computing that it's hugely in demand even if there's little evidence that it actually matters for anything important.
Agreed. I'm actually getting really bored of this "X programming paradigm is bad" trope. I've written code that works in imperative, OO and functional styles, but the really important point in all cases is that the code worked: it solved some problem, it added value to the user. For me it's about using the right tool for the job in hand. Plus there's the observation that learning to program in a different style may well make you a better programmer in the other styles too.
As long as you are alone and in control, I agree.
But in a project with a large java inheritance hierarchy, polymorphism maxed out by some remote developers, it is easy to really get lost.
In a team you obviously need to agree on a set of languages and tools but, again, I still find people using different styles, often depending upon the functionality they're working on.
To some extent it depends on what you think is important, and the environment you're working in: I always want to get working software shipped and I'm happy to change the way I work (and the tools I use) to get this done more efficiently. If you believe there's ONE TRUE WAY - or perhaps you have team members who insist that's the case* - that's bound to be more of a challenge.
*I've never found this mentality leads to the best outcomes, especially not on large, complex projects, and certainly it can cause a lot of friction within the team.
Programming languages are the interface between the computer and fallible humans. Thus it is in fact very difficult to prove that a particular paradigm is better or worse.
Civil engineering has been around for centuries. Yet the Tacoma Narrows Bridge failed in a completely unexpected way.
This passed down engineering "memory" or "lore" is invaluable. Every engineer should endeavour to learn from their mistakes and successes
The problem with OOP is that doing it at all is so easy. Its abstractions makes a lot of sense to beginners and experts alike. Its biggest flaw is that doing it well is so fantastically hard. It requires a level of discipline and experience that most developers simply never attain, because they don't do it long enough. I don't think 1% of the worlds OO developers have it (I sure don't after more than 10 years of full time OOP).
The trick to sane OOP is doing as little of it as possible. When you maximize the number of pure methods, minimize mutable state, minimize inheritance and so on, you are doing OOP well.
> When you maximize the number of pure methods, minimize mutable state, minimize inheritance and so on, you are doing OOP well.
Odd, that's also how you do functional programming well.
(As regards functional programming languages not having inheritance as such, well, if you don't do any inheritance you're certainly minimizing it.)
More and more, I feel that mutable state is really an advanced topic and should be treated with the kind of suspicion we usually reserve for multiple indirection and undisciplined use of the goto statement.
> I feel that mutable state is really an advanced topic and should be treated with the kind of suspicion we usually reserve for multiple indirection and undisciplined use of the goto statement.
This. Mutable state is the goto of the 90's. Something that linters and compilers of the future will warn us about, just like we today might have to put pure pointer arithmetic in C# inside some unsafe block because we need to express that "trust me I know what I'm doing".
When you maximize the number of pure methods, minimize mutable state, minimize inheritance and so on, you are doing OOP well.
But when you write that style of OOP, what's the advantage over a non-OOP language that is immutable by default, uses just functions, and provides constructor (in the FP sense) hiding to maintain invariants?
To me, it seems that it only has disadvantages: team members can still write mutable, reference/pointer leaking horrors.
I was about to comment that SQL normalization and functional programming (even in diluted form through closure friendlyjavascript) taught me OOP more than OOP itself.
There are deep principles that overrule paradigms and they're rarely explained via OOP.
Factorization of logical dependencies (normalization) is a generic form of object modeling.
Logical regression over function yields combinators. (like how one derives Y from factorial)
Thinking in terms of properties instead of memory, unless you reach interface only OOP you don't do it (OOP in the Java/UML sense gives you rope to bike shed to no end, effectful constructors, is it composition, aggregation ... a never ending well)
The notion of eta conversion, that I see as a generalized form of 'unnecessary wrapping' (if true then true else false and such) that is clean and well expressed in FP and full of bolts in Java (anonymous classes are too verbose, .Net had delegates for this I believe but it's named as yet another kind instead of an equivalence).
Linked to reliance on temporaries instead of combinators
a = f ..
b = g a
c = k c
return c
instead of simply
(k . g . f) a (or a |> g |> ... for reading order )
ps: I encourage people to read books like Programming in Haskell from Graham Hutton (this book deals with average problems, not too sophisticated alienating ideas) to get a feel on how so many things can be re-expressed as composition.
> But when you write that style of OOP, what's the advantage over a non-OOP language that is immutable by default
I think there is no advantage to using an OOP language with mutability by default, over not doing so. (The only advantages are circumstantial -- the most common are probably that the existing code is OO, or that the existing devs are OO, or any devs we can find are OO, or the company is a shop that has always done OO, etc. ).
I didn't say that all state should be immutable. Sometimes you actually must have mutable state (e.g. in a large scale game engine or scientific app where performance and cache is king, you likely do want to modify in place, which is hard to do with immutable abstractions over the data). The trick is to use as little mutable state as possible for the task at hand.
I certainly hope most new statically typed languages will not use mutable by default. Rust is a good example. Rust does most of what OO and FP does well, but does it without actually being either FP or OO in the normal sense.
My experience of OO programming - by the end I found the following style worked best:
-> always using pure abstract base classes
-> constructed those implementations using factories (which in turn, were pure abstract base classes)
-> almost always made my implementations immutable
which after a while, I realised, was just functional programming with more boiler-plate.
Not mutable, but tethered state, I think OO/Objects/Classes work slightly better when used as a means of tethering state/presentation/events... not that they need to mutate... for example the React.Component via ES6-style classes doesn't bug me as much as some... but I use it sparingly, most of the rest is more functional.
* Ad-hoc polymorphism is valuable, and objects represent a clear way to express it
* Thin wrappers that slightly change functionality are often a business requirement, and "extends" inheritance provides an easy way to express them
* Having data associated directly with the corresponding state can make exploratory programming easier (e.g. suppose I have a library for accessing the Facebook API, so I fetch my user info. In OO style it's immediately clear what I can do with my user info, because it's all methods on the object; in functional style it's less clear)
> Thin wrappers that slightly change functionality are often a business requirement
I doubt that a business requirement stipulates how something is implemented, but thin extensions are often an apparently convenient way of quickly getting the slightly different behavior of the business requirement. Whether or not it's a good idea just because it is quick and convenient I think isn't clear. From a business perspective this is one of the false benefits -- if the problem is apparently solved quickly, but the maintenance is harder afterwards, it may not be the best idea. I'd bet sum types + common pure functionality accomplishes the same thing with lower maintenance cost.
> Having data associated directly with the corresponding state can make exploratory programming easier
I agree that many functional languages have definite drawbacks in terms of exploratory and self documenting programming. "Methods" on types are to me a definite requirement also for an FP language because of autocomplete/documentation/ergonomics. To , me writing v2 = Vector.normalize(v1) is vastly inferior to v2 = v1.normalize(). This is syntactic sugar of course, but important sugar. Neither is "OO" though: associating methods as postfix can be done regardless.
> I'd bet sum types + common pure functionality accomplishes the same thing with lower maintenance cost.
Not my experience - most functional languages make it much harder than it should be to say "this is exactly an X except that it prints differently".
> I agree that many functional languages have definite drawbacks in terms of exploratory and self documenting programming. "Methods" on types are to me a definite requirement also for an FP language because of autocomplete/documentation/ergonomics. To , me writing v2 = Vector.normalize(v1) is vastly inferior to v2 = v1.normalize(). This is syntactic sugar of course, but important sugar. Neither is "OO" though: associating methods as postfix can be done regardless.
Well, if a feature is present in all mainstream "OO" languages and not in any mainstream "functional" languages then I'll call it "OO". I'd be very interested to see a functional language with good support for this kind of thing though.
> Not my experience - most functional languages make it much harder than it should be to say "this is exactly an X except that it prints differently".
yes, FP usually makes it harder. The question is whether the extra effort is worth it over a 10 year period or not, i.e. does the FP solution maintain more easily than the OO one? I'm not saying it does, just saying the answer isn't clear after the implementation, only much later.
> I'd be very interested to see a functional language with good support for this kind of thing though.
> Not my experience - most functional languages make it much harder than it should be to say "this is exactly an X except that it prints differently"
That's because this is the wrong thing to say in the first place - it dilutes the very meaning of “type”. You should factor out the common parts, and only then say X prints this way, Y prints that other way.
The overhead of adding another type is nontrivial (which maybe is a problem in itself; nevertheless, here we are). For business-critical distinctions it's worthwhile. For differences that only matter in one or two places, or for less-critical functionality (e.g. GUI tweaks) it may not be.
Ad-hoc polymorphism is valuable, and objects represent a clear way to express it
You can do ad-hoc polymorphism using type boxes (existentially quantified type classes). Of if you think this is an anti-pattern, just plain old data structures plus (first-class) functions:
I've found composition is always much more heavyweight than it should be. "Deriving" helps a bit, but it still feels very manual when your use case is along the lines of "I have this thing with 6 behaviours and now I want a wrapper that changes just one of them very slightly".
A more practical way of discussing this would be a concrete example of a business need, implemented in both paradigms - object-oriented design relying on inheritence and then a more typical to FP design based on composability. Then by simply comparing the two we could draw some conclusions about code size, simplicity, maintainability and so on.
There's no way to do a small example because it's an issue that only happens when you already have a large codebase. Say you have a large, complex object - and you can argue that's already assuming something about your development path. And then you want to offer a variant that has slightly different behaviour (say it has an extra line when you print it, and one of the calculation methods should return 2x as much for this variant, and another method should return 0.5x as much - and again, that's making intrusive assumptions about the wider design). That kind of scenario really does happen all the time in real-word functional code (at least for me), but it's impossible to make it a rigorous example where partisans can't say "don't do that then".
"The trick to sane OOP is doing as little of it as possible. When you maximize the number of pure methods, minimize mutable state, minimize inheritance and so on, you are doing OOP well."
Maybe...
The attempt to forbid (global) state, as written in the op, is honourable, but I think, if a developer wants state, (s)he'll find a way.
There is no good code without good developers. So lets go on educating ourselves.
Erlang's creator, Joe Armstrong, said "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." (Third point at http://www.infoq.com/interviews/johnson-armstrong-oop)
So maybe OO is not that bad, it's the so called OO languages that didn't go all the way to implement it properly. Still they've been very successful so far. It means that many people liked what they had to offer. This might be about to change even quickly but I won't be so harsh agaist what we have under our fingerprints now. Not perfect but not a disaster.
(Of course, I mean that "Everyone knows precisely what OO is, but if you get enough of them in the same room you see distinct camps forming, each with their own definition of OO which is incompatible in some respect with the definition every other camp has.")
That interview is about both Erlang and Smalltalk. Ralph Johnson, not one of Smalltalk's designers but close to what we'd call an evangelist nowadays, speaks about Smalltalk. Unfortunately I can't find one sentence that captures his view about Smalltalk's OO. I quote Johnson about Erlang, and in a way about Smalltalk itself:
"The thing about Erlang is that it's in some sense 2 languages, at least you program it 2 levels because one is the functional language that you use to write a single process and then there is what you think about all these processes and how do they interact, one process is sending messages to the other. At a higher level, that Erlang is object oriented, at the lowest level it's a pure functional language and that's how it got advertised for a long time."
[...]
"The only way in Smalltalk to interact with an object is send it a message, but the issue is what message do you have. It's the same thing in Erlang"
So it seems to me that he's saying that Erlang's processes are close to Smalltalk's objects.
I recommend reading all the transcript (much quicker than watching the video).
I watched a talk recently with Bjarne Stroustroup where he claimed he never developed C++ as an "Object Oriented" language, just that it had some OO based abstractions. Apparently it got labelled as such at the time because OO was in vogue but it has many great features quite apart from that. His intention was that it just be a "better C" and be used as such.
Depends on who gets to decide what object oriented means. Simula was out before Smalltalk. Most will call that the first object oriented programming language but it did not rely in message passing but rather worked similar to C++ and Java. Which isn't odd since Bjarne based C++ on his work with Simula.
“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.” – Joe Armstrong
Java allows static functions that aren't tied to objects. They are defined in classes, but the classes just serve as namespaces to prevent naming collisions. Those static methods aren't OO in any meaningful sense.
There have been three main movements or philosophies in programming. The first was the "structured programming" movement starting in the late 50s, discussed in the article. This was at the time when high-level languages (for a weak definition of high-level) were first becoming usable, and most people were used to using assembler. Structured programming gave some structure (hence the name) to the control flow methods used at the time. This is where for and while loops come from. "GOTO Considered Harmful" is one of the main texts from this period, and I think we can say by the 70s structured programming had won over the mindshare of most developers.
The second is object-oriented programming, which started in the late 60s, and achieved mindshare in the mid-80s to early 90s. The most influential OO language is Smalltalk (1983).
The third is functional programming, which either started in the 50s or the 70s (depending on where you draw the line) and is in the process of winning mindshare from OO right now. It's important to note that the modern incarnation of functional programming (by which I mean static types + abstractions like monads and applicatives) is very new, in programming language terms. The main standard for Haskell is 1998, Scala was created in 2005, and key techniques such as free monads are the subject of ongoing research. It has only been viable to do production quality statically typed FP for about a decade.
Python was created in the early 90s, and looks back to Smalltalk for inspiration. Culturally Python is (obviously) in the OO camp. Culturally, most developers are in the OO camp as well. Thus their definition of "good" is OO, and Python fits this. The linked post is a sign of the change that is happening right now---more developers are discovering FP and their definition of good is switching from OO to FP. From the perspective of an FP proponent, Python is not a good language.
The key point I'm trying to make is that programming is a cultural movement. We like to pretend we're objective and scientific, but we aren't. That's ok. Neither is science (see, e.g., "The Structure of Scientific Revolutions"). I do believe, however, that we're improving over time (said as a true FP adherent, of course :-)
Interestingly, Lisp tends to be more closely related to modern imperative or OOP languages than modern FP languages most of the time (although this is very much a matter of dialect, as Clojure and Scheme code tend to resemble FP much more than CL and Elisp code do). Lisp had some ideas influential to FP that also made their way into other types of programming (garbage collection, closures, etc.), but things which are considered a modern necessity for functional programming are not present in most dialects of Lisp (like immutability of data, at least by default, currying, etc.). In fact, most Lisp dialects don't even have first-class functions (in the sense that variables and functions are stored in totally different namespaces), which even languages like C# support. So to call Lisp 'functional' is a bit of a stretch, despite the large divides between different dialects and the multi-paradigm abilities of those languages, but that's not to say that it wasn't hugely instrumental in the onset of functional programming.
I think another way to look at it is that Lisp took untyped lambda calculus and turned it into practical programming language, although in an opinionated way that was necessary at the time for performance reasons.
However, modern FP languages always start from some form of typed lambda calculus, because it has more desirable properties.
nv-vn has given a good explanation. When I said "[FP] started in the 50s or the 70s (depending on where you draw the line)" I was implicitly referring to Lisp (1958) and to Scheme and ML (1970s). Although Scheme does not have a static type system it is culturally a functional programming language and has had a large influence on the field.
Python still suffers from unrestricted mutation, which isn't helped by the lack of a type system. And I say this as someone who primarily uses Python.
If anything, Smalltalk is the closest to "everything is an object". It doesn't even have an "if" statement, it's just a method call. Even there, the problem of mutation is present.
I've written a fair bit of Python code, but I can't recall the time I last wrote a "class" in Python, and when I did, it was just a simple struct-type object with no inheritance or anything like that.
Most of the time my "objects" are just tuples, lists and dictionaries, with free functions to manipulate them.
In general, I dislike attaching complex semantics to types because that makes code more difficult to read. When you look at a function call and can't tell where it's leading by just looking at the code, there's an instant loss in productivity. At worst, you have to open the debugger and step through the code. This is particularly annoying when the problem domain requires no such abstractions, but they're in place just to shave a few lines of code.
I feel the same way as the author. The dependencies are inherent in the problem, and no amount of abstractions is going to remove them. So worrying whether you should split them this way or that way (or how you should structure your "objects") is a false worry.
I think the mathematicians got it right with typed functional programming, which has a rigorous basis. I think OOP is flawed because it's not formal enough.
I think there's a fundamental misconception at the heart of how people think about OOP, and it revolves around "what is an object". In all likelihood OOP perhaps became hyped because it seemed to provide a paradigm whereby one could develop programs as simulations that resembled the real-world systems they were modelling. This is somewhat similar to the way COBOL took off because it was intended to facilitate verbose readable code that would be verifiable by analysts.
I think this may have been the original intention of real OOP languages (OO theory itself developed as a way of modelling knowledge from human perspective [1]) such as Smalltalk but as the author says it just isn't feasible to do this "all the way down". In my experience OOP of this style is best used as a high level abstraction but beneath that for the sake of pragmatism and performance you usual need to get a bit more imperative.
The other perspective on OOP is that one models your solution space rather than your problem domain. One models data such as `CarHoodDrawingAlgorithm` rather than `CarHood` - and it's this reasoning that can lead us towards functional programming, which may be described as "OO for functions".
OO is still useful, because objects are something we naturally understand and can discuss. "Oh I got an exception when I called 'draw' on CarHoodDrawingAlgorithm / Oh really, that's a subclass of CanvasDrawer which I wouldn't have expected to throw that type of exception"
I think it breaks down where you have a mismatch between what different parties understand what an object to be, and how pervasive the paradigm needs to be. Quite rightly advanced programming topics are moving towards FP which more specifically answers the concerns in the solution space, which isn't I believe what OO was meant for.
Yup, I heard a lot about objects and object oriented programming (OOP). On some selected toy problems it seemed to have some advantages. Okay.
It's good to read from the OP and in this thread yet more explanations of what OOP was supposed to be about. Okay.
So, in my current project, I make a lot of use of the classes in .NET and have defined some classes with some methods, etc. Okay -- the encapsulation seems to help make the code easier to understand and debug.
I have made some use of polymorphism, and mostly I like it. So, I use interfaces, and, really, those are a lot like, but much less powerful than, what I used to do in languages where I passed as an argument to a subroutine or function the name of a subroutine or function the subroutine or function could call. E.g., in solving an initial value problem for an ordinary differential equation, have a function that does that and pass to the function a function that will evaluate the differential equation. For finding the minimum value of a function X, have a function Y that is a general purpose function minimizer and pass X to Y and let Y do the work of finding the minimum value of X.
For inheritance, .NET seems to use a lot of it, at least in their documentation, but so far in the code I've written I've never used any of inheritance. I love hierarchical file systems and the idea of inheritances of capabilities and access control lists (ACLs), but I don't really like the idea of inheritance among OOP classes. Sorry 'bout that.
If I want to build an outhouse, then I don't want to inherit a small house and add a toilet -- instead, I just want an outhouse.
For passing messages, sure, I saw that in Smalltalk but never thought to use it among objects in one program.
My server farm architecture and software does have some message passing among some of the back end servers. For a case of that, sure, OOP helps: For the data to be passed, I define a class, allocate an instance, put data into the members of the instance, serialize the instance to a byte string, send the byte string via just old TCP/IP sockets, wait for the response, a byte string, deserialize the byte string to an instance of the class that is supposed to get the returned data, and continue on. If there is an error, then the code writes a message to the Web site log file and returns a notification of an error to the user. Works fine.
For immutable state, no thanks -- e.g., in software about a car, the current state is position and velocity, and as the car moves that state changes and is not immutable. The speedometer of the car does not have immutable state, either, nor the steering, throttle setting, transmission gear, etc. Sure, could define an immutable state separately for each instance of time, but I guess that speedometer makers didn't think of that. If I put some raw meat in the oven and cook it, what comes out should be a good roast, and not something immutable. I think of state as changing, not as immutable.
Some of what the OP describes that is supposed to be real OOP, I was never tempted! No thanks! I never tried that and never would. To me, those rules were obviously nonsense, like programming standing on my head. Able to do it? Yes. Want to do it? No. A good idea anyway? Nope. Good to read that author of the OP now agrees!
But that de/serialization to/from a byte string, I like that! The .NET classes, sure, they are very nice to have. For sending data from one server to another, sure, define a class, allocate an instance, assign values to the members, and send the data -- terrific, easy to understand, works great. E.g, the data I send might be fairly complicated, but classes usually have enough generality that can define good places for all the complicated data.
And, can have arrays of object instances -- terrific! And one of the members of a class can be an instance of another class -- I do some of that.
I think you just invented Realism in programming. The programs are written by programmers not academics. There is no perfect way to code something. We should stop the dogma fetishism. Structural/OO/Functional programming theories have all their pros/cons but it can't be proven that one is better than the other. So instead of committing ourselves to the one true cause, we should focus on writing code that achieves its purpose. Why shouldn't we use goto statements at all?
PS: I wanted to write "programming is not sacred", but I couldn't. I only use emacs and open source software. I love computers religiously and think that there is something magical about them. So I admit to a certain extent of hypocrisy.
We computer geeks are obsessive people.
Terrific! You named it -- Realistic Programming! Now you will be as famous as Dijkstra, Wirth, Knuth, Stroustrup, etc.!
Yes, I saw some sense in some of the criticism of the GOTO statement, but, yes, I still use the GOTO statement.
Main usage: In a function, some bad situation is detected. Okay, that code might write to a log file and should set a return code value. Then that code just does
GOTO OUT
which means, time to give and just get out'a here, ASAP,
where OUT is a statement label of some code that does whatever general clean up is needed and just returns.
There's another cute use of nearly a GOTO: For a loop, have something like DO FOREVER and in the code of the loop, maybe several places, decide when to get out'a the loop and then, do so with a statement LEAVE or some such which is really a GOTO the next statement after the end of the loop. It's essentially a GOTO and in some languages is implemented with an actual GOTO.
But there was a totally sweetheart use of a GOTO in PL/I: In some code could have a statement
ON FUBAR
where FUBAR was a condition that could be raised, and this statement ON was followed by some code to be executed if
condition FUBAR was raised. The ON statement made the condition FUBAR enabled and established what to do if the condition was raised. The code of the ON statement could have a GOTO.
So, the code of function A has such an ON statement. Function A is called and the execution comes to the ON statement. Now the condition FUBAR is enabled and that ON unit is established for condition FUBAR should it be raised. If the code of function A returns, then that ON condition is no longer established.
Function A calls function B which raises condition FUBAR.
Then the code immediately jumps to the code of ON FUBAR. If that code executes a GOTO to a label in the code of function A, then all the code in the stack of active code from function A and lower is ended (e.g., storage automatically allocated is freed), and execution continues.
So, look, Ma, a way to handle exceptional conditions that is easy to code and understand and does the usual, obvious stuff to clean up the mess for, e.g., no memory leaks.
And could do GOTO X, and the X could be a statement label in
any code so far called but not yet returned, and the label in the code most recently called but not yet returned would be the target of the GOTO. Again would get stack cleanup. Nice.
Ah, Dijkstra would roll over, screaming.
GOTOs are a common part of life: If get a flat tire, then raise an exceptional condition, cancel a lot of plans and work in progress, and go to the shop of the towing service and call a cab. If the computer processor fan stops and the processor overheats, then something similar. Lots of common situations in life.
OOP is a tool, like FP. Sometimes it make sense, sometimes it doesn't. In my opinion, a good language should allow to do both. Encapsulation and polymorphism are obviously the most important things in OOP. I think what made some projects hard to maintain is the over reliance on inheritance. If Java had something like mixins or traits , developers would rely less on inheritance to achieve polymorphism. I wish Go had true traits for instance ( struct embedding are not traits ) , it would have made the language close to perfect.
"when I have umpteen Manager objects, I then need a ManagerManager"
Actually, when this happens it only makes sense for you to get inspired from the real world again and end up designing a hierarchy called "bureaucracy"! </sarcasm>
I am so tired of hearing "xxx is bad". It's always the same story:
- It starts out as a good and useful idea for certain problems.
- Then some people elevate it to the solve-it-all method and if you do anything else you are a bad person. Almost like a religion
- After a while nobody remembers why this thing has been invented and people just do it because they have to without any understanding why
I have seen this happen to:
- OOP (works great for a lot use cases if used in moderation but not all)
- Flat design
- Scrum/agile
- MVC
- Javascript
- Goto
- Loops (everything has to be a closure)
Right now functional programming is cool. Just wait until the people who have messed up OOP get onto the FP bandwagon. Clueless people will start doing FP and things will go to hell.
In the end you need to know what you are doing and why. There is no magic methodology that will save you from that.
82 comments
[ 1074 ms ] story [ 2944 ms ] threadI'm fairly pragmatic when it comes to OO, FP etc.. I happen to like modern JS, I like FP for most workflows, and will use classes where it makes sense. By not needing to create DI systems and other "enterprise" patterns early on, I'm able to create simpler solutions... in most cases FP/Procedural hybrids make more sense in terms of workflow/state ... for state that is tethered to UI context, but no necessity to persist/reload will use more class oriented approach (React + Redux for example).
Once you're past the initial boilerplate and concepts, adding features adds far less complexity this way than with more traditional OO approaches.
I think with C++ I had also reached the point where I realized it was not just difficult to scale (I had worked on ginormous codebases) it was also difficult to 'control'... Adding new people on the project could sometime mean disasters that would go unseen for a while before exploding in your face...
Newbie programmer decides it's a cool idea to go and 'tweak' the String class - or any other base class- or other equivalent 'good idea' that sometime lead to huge amount of effort to understand the weird ripple effects afterward, and then 'fix' so it's done 'properly'.
There was also the 'design for design' problem where a 1/2 page feature that was ever going to be used ONCE was designed with an imbroglio of 5 or 6 classes just to match some person's idea of a 'design pattern'.
I've now 'reverted' to mostly 'procedural' C; however, quite a lot of the 'good' concepts of C++ for encapsulation can be done in C without all the faffing around, and it's a LOT harder to fsck up and create ripple effects; you can 'layer' modules instead of 'inheriting' with the equivalent compartmentalization you'd want from C++, without the 'dangers' of inheritance.
Sure, it lacks syntaxic sugar, and sure, there's still stuff I miss; but it does scale pretty well, and the footprint difference is fantastic.
Changing the programming language won't prevent newbie programmers from wreaking havoc in your code base, nor improve the software design skills of your colleagues.
On the other hand, I do agree that programming languages have a "culture" which defines the idioms that are accepted, patterns, attitudes towards various practices. My feeling is that hyper-oop programmers have long moved on to Java and .NET.
C has a culture of keeping things simple, because of its long history and limited abstraction possibilities. It's also the favorite refuge of people that hate OOP, so no surprise you won't see over-engineered OOP hierarchies.
You cant go and declare some basic tool classes only editable by a expert/architect or a newb under "SignOff" Supervision. There is also no real metric on expertise, and a way to prevent non-experts to go banana on something beyond the scope of there fields. You do not want a assembler coder going first time OO and in reverse. What actually would help is a sort of guild - where one has to accomplish certain feats first, before beeing allowed to level up..
Then there is the minefield of how to break the news to someone that they can't be trusted 'just yet', with the discrimination minefield that implies etc.
And in many companies, you can't even go an tell a colleague that their change is in fact, crap, and has to be redone properly. Never been in a company where that didn't create huge waves, regardless of the level of proof you can attach to it.
I hear in some companies (google?) it's expected, but it's an exception, in my experience...
How can we know if we should switch to a different paradigm? Empirical data. So please, academic community and the software industry: go out and collect that data! Theoretical (let along religious) arguments are perhaps a valid starting point, but they are ultimately unhelpful in making a decision. In fact, it has been mathematically proven that they can contribute little to the discussion: the theoretical difficulty in constructing a correct program is the same regardless of the abstractions used[3]; only cognitive effects -- which can only be studied empirically -- could provide arguments in favor of a certain paradigm making it easier for humans to write correct code.
[1]: As someone who worked on software written in procedural code before the popularity of OOP, it wasn't any better (actually, it was worse). For contemporary examples, see Toyota's car software.
[2]: Those are, of course, proxies for "very complex requirements, both functional and structural", but the point stands.
[3]: The cost of verifying/constructing a correct program is at least linear in the size of the program's state-space (or, more precisely, its Kripke structure, which also includes transitions) regardless of representation in code or abstractions used to make it more succinct. E.g., "Kripke structures that admit a succinct representation are not simpler for model checking purposes than arbitrary Kripke structures" (http://www.lsv.ens-cachan.fr/Publis/PAPERS/PDF/Sch-aiml02.pd...), and model checking can be reduced to any other verification/construction methodology.
Someone is defending against some pedantic stalker… :-)
First of all because there are counterexamples. E.g. I think SQL is generally well-liked for its domain and widely used.
Secondly, even if we complain about languages that are used, we can still distill and compare pros and cons. Speaking of C++ specifically - once Rust is in wider use, people will probably complain about it too. But we can describe in what regards Rust is safer objectively.
It shouldn't be very difficult to prove that a particular programming paradigm is better along some axis than another. I think it's great that Brian is happy and more productive, but that has no meaning to me if I want to make an informed decision about FP.
I think it would be one of the hardest things to ever prove scientifically actually. Basically the only valid method I'd accept is having a large group of people who are non-developers, then train half of them in one paradigm and the other in another. Then have both groups solve the same set of problems.
The problem with this experiment is that in order to get useful results is that you need a good group of people that are representative of programmers (i.e. people that don't know programming, but can and are willing/able to learn). Say you can find 1000 mechanical engineering students for example. Great. Next they need to be trained. How long does that take? A year or two? Finally they need to solve the problems, which should be large problems. Next the solutions to the problems should be carefully evaluated. I'd say the experiment if done in academia would be ridiculoulsy expensive and time-consuiming.
Numerous studies such as on static vs dynamic typing have used tiny programming experiments done by a small groups of students doing trivial exercises over the course of a day. I'd say they give little or no insight in how experienced developers handle large problems.
It's very hard to do it as a natural experiment too. A large company could try giving the same task to a team of OO developers, as it does to a team of FP developers. This has happened and the result is usually that the FP developers do it in half the time with half the bugs and twice the enthusiasm. However -- can we know that the people who ended up being FP developers weren't just better developers than the OO devs? I'd wager that "enthusiasts" are more commonly drawn to FP today, so the base of FP developers is a group that is on average "better" than the OO devs. So while I don't doubt that FP is "better" in most scenarios, I also belive the FP group would have done a better job with the OO language as well.
I'd settle for two competitors working in the same domain producing similar products using different paradigms, reporting significantly different costs throughout the project's lifetime (including maintenance). Or even a single company reporting a vast decline in costs after switching paradigms, provided that the project is big enough and lasts long enough (five years at a minimum, a decade is preferable). Of course, the more domains and more cases per domain the better. This isn't theoretical. We had numerous case studies of this sort in the late '90s-early '00s showing a very big cost reduction when moving from C/C++ to Java, in projects ranging from enterprise software to defense.
I think you'll easily find the example you seek. In fact I think you'll probably find several.
Unfortunately they are all likely to support different conclusions.
... which means that the case is not so clear cut, which may suggest that the increased effectiveness of some methods is not universal but restricted to specific domains or even specific team compositions.
A company switching paradigms is a better test, since the same developers are involved, and presumably they would be less experienced in the new paradigm than the old. If they still produce better software that is a good indication that it was a good switch. Still, there may be a number of companies that did this and failed because they couldn't reach profitability with a new code base, or couldn't fill vacancies and so on. Like I said, it's very hard.
Yes, but over enough such cases, you can average this out. Also, better developers are more expensive, so that is factored into the cost.
... and have them maintain the resulting software for years and years. Otherwise you're mostly "just" looking at learning curve, I should think.
It's a sort of weird testament to the efficiency (not efficacy) or inscrutability of computing that it's hugely in demand even if there's little evidence that it actually matters for anything important.
Right?
To some extent it depends on what you think is important, and the environment you're working in: I always want to get working software shipped and I'm happy to change the way I work (and the tools I use) to get this done more efficiently. If you believe there's ONE TRUE WAY - or perhaps you have team members who insist that's the case* - that's bound to be more of a challenge.
*I've never found this mentality leads to the best outcomes, especially not on large, complex projects, and certainly it can cause a lot of friction within the team.
Civil engineering has been around for centuries. Yet the Tacoma Narrows Bridge failed in a completely unexpected way.
This passed down engineering "memory" or "lore" is invaluable. Every engineer should endeavour to learn from their mistakes and successes
The trick to sane OOP is doing as little of it as possible. When you maximize the number of pure methods, minimize mutable state, minimize inheritance and so on, you are doing OOP well.
Odd, that's also how you do functional programming well.
(As regards functional programming languages not having inheritance as such, well, if you don't do any inheritance you're certainly minimizing it.)
More and more, I feel that mutable state is really an advanced topic and should be treated with the kind of suspicion we usually reserve for multiple indirection and undisciplined use of the goto statement.
This. Mutable state is the goto of the 90's. Something that linters and compilers of the future will warn us about, just like we today might have to put pure pointer arithmetic in C# inside some unsafe block because we need to express that "trust me I know what I'm doing".
But when you write that style of OOP, what's the advantage over a non-OOP language that is immutable by default, uses just functions, and provides constructor (in the FP sense) hiding to maintain invariants?
To me, it seems that it only has disadvantages: team members can still write mutable, reference/pointer leaking horrors.
There are deep principles that overrule paradigms and they're rarely explained via OOP.
Logical regression over function yields combinators. (like how one derives Y from factorial)
Thinking in terms of properties instead of memory, unless you reach interface only OOP you don't do it (OOP in the Java/UML sense gives you rope to bike shed to no end, effectful constructors, is it composition, aggregation ... a never ending well)
The notion of eta conversion, that I see as a generalized form of 'unnecessary wrapping' (if true then true else false and such) that is clean and well expressed in FP and full of bolts in Java (anonymous classes are too verbose, .Net had delegates for this I believe but it's named as yet another kind instead of an equivalence).
Linked to reliance on temporaries instead of combinators
ps: I encourage people to read books like Programming in Haskell from Graham Hutton (this book deals with average problems, not too sophisticated alienating ideas) to get a feel on how so many things can be re-expressed as composition.I think there is no advantage to using an OOP language with mutability by default, over not doing so. (The only advantages are circumstantial -- the most common are probably that the existing code is OO, or that the existing devs are OO, or any devs we can find are OO, or the company is a shop that has always done OO, etc. ).
I didn't say that all state should be immutable. Sometimes you actually must have mutable state (e.g. in a large scale game engine or scientific app where performance and cache is king, you likely do want to modify in place, which is hard to do with immutable abstractions over the data). The trick is to use as little mutable state as possible for the task at hand.
I certainly hope most new statically typed languages will not use mutable by default. Rust is a good example. Rust does most of what OO and FP does well, but does it without actually being either FP or OO in the normal sense.
which after a while, I realised, was just functional programming with more boiler-plate.
* Thin wrappers that slightly change functionality are often a business requirement, and "extends" inheritance provides an easy way to express them
* Having data associated directly with the corresponding state can make exploratory programming easier (e.g. suppose I have a library for accessing the Facebook API, so I fetch my user info. In OO style it's immediately clear what I can do with my user info, because it's all methods on the object; in functional style it's less clear)
I doubt that a business requirement stipulates how something is implemented, but thin extensions are often an apparently convenient way of quickly getting the slightly different behavior of the business requirement. Whether or not it's a good idea just because it is quick and convenient I think isn't clear. From a business perspective this is one of the false benefits -- if the problem is apparently solved quickly, but the maintenance is harder afterwards, it may not be the best idea. I'd bet sum types + common pure functionality accomplishes the same thing with lower maintenance cost.
> Having data associated directly with the corresponding state can make exploratory programming easier
I agree that many functional languages have definite drawbacks in terms of exploratory and self documenting programming. "Methods" on types are to me a definite requirement also for an FP language because of autocomplete/documentation/ergonomics. To , me writing v2 = Vector.normalize(v1) is vastly inferior to v2 = v1.normalize(). This is syntactic sugar of course, but important sugar. Neither is "OO" though: associating methods as postfix can be done regardless.
Not my experience - most functional languages make it much harder than it should be to say "this is exactly an X except that it prints differently".
> I agree that many functional languages have definite drawbacks in terms of exploratory and self documenting programming. "Methods" on types are to me a definite requirement also for an FP language because of autocomplete/documentation/ergonomics. To , me writing v2 = Vector.normalize(v1) is vastly inferior to v2 = v1.normalize(). This is syntactic sugar of course, but important sugar. Neither is "OO" though: associating methods as postfix can be done regardless.
Well, if a feature is present in all mainstream "OO" languages and not in any mainstream "functional" languages then I'll call it "OO". I'd be very interested to see a functional language with good support for this kind of thing though.
yes, FP usually makes it harder. The question is whether the extra effort is worth it over a 10 year period or not, i.e. does the FP solution maintain more easily than the OO one? I'm not saying it does, just saying the answer isn't clear after the implementation, only much later.
> I'd be very interested to see a functional language with good support for this kind of thing though.
F# is a good example.
That's because this is the wrong thing to say in the first place - it dilutes the very meaning of “type”. You should factor out the common parts, and only then say X prints this way, Y prints that other way.
You can do ad-hoc polymorphism using type boxes (existentially quantified type classes). Of if you think this is an anti-pattern, just plain old data structures plus (first-class) functions:
https://lukepalmer.wordpress.com/2010/01/24/haskell-antipatt...
Thin wrappers that slightly change functionality are often a business requirement, and "extends" inheritance provides an easy way to express them
So does composition.
In OO style it's immediately clear what I can do with my user info, because it's all methods on the object
The problem with this is discussed in the linked article.
Edit: I see that you do Scala, so all of this is not news to you ;).
I've found composition is always much more heavyweight than it should be. "Deriving" helps a bit, but it still feels very manual when your use case is along the lines of "I have this thing with 6 behaviours and now I want a wrapper that changes just one of them very slightly".
Maybe...
The attempt to forbid (global) state, as written in the op, is honourable, but I think, if a developer wants state, (s)he'll find a way. There is no good code without good developers. So lets go on educating ourselves.
So maybe OO is not that bad, it's the so called OO languages that didn't go all the way to implement it properly. Still they've been very successful so far. It means that many people liked what they had to offer. This might be about to change even quickly but I won't be so harsh agaist what we have under our fingerprints now. Not perfect but not a disaster.
http://c2.com/cgi/wiki?DefinitionsForOo
http://c2.com/cgi/wiki?NobodyAgreesOnWhatOoIs
(Of course, I mean that "Everyone knows precisely what OO is, but if you get enough of them in the same room you see distinct camps forming, each with their own definition of OO which is incompatible in some respect with the definition every other camp has.")
"The thing about Erlang is that it's in some sense 2 languages, at least you program it 2 levels because one is the functional language that you use to write a single process and then there is what you think about all these processes and how do they interact, one process is sending messages to the other. At a higher level, that Erlang is object oriented, at the lowest level it's a pure functional language and that's how it got advertised for a long time."
[...]
"The only way in Smalltalk to interact with an object is send it a message, but the issue is what message do you have. It's the same thing in Erlang"
So it seems to me that he's saying that Erlang's processes are close to Smalltalk's objects.
I recommend reading all the transcript (much quicker than watching the video).
See also
Why OO Sucks - by Joe Armstrong
http://harmful.cat-v.org/software/OO_programming/why_oo_suck...
Isn't Python widely considered a good programming language, and isn't everything in Python an object?
How does this fit with OOP being bad?
* You can write a script that's "just" a script, it doesn't have to be an object with a main method or anything like that
* You can have free-floating functions and values at top level
* Modules are first-class and not really objects in the classical OO sense
* Python culture favours using tuples or maps as records, and free-floating functions, over using objects for everything
There have been three main movements or philosophies in programming. The first was the "structured programming" movement starting in the late 50s, discussed in the article. This was at the time when high-level languages (for a weak definition of high-level) were first becoming usable, and most people were used to using assembler. Structured programming gave some structure (hence the name) to the control flow methods used at the time. This is where for and while loops come from. "GOTO Considered Harmful" is one of the main texts from this period, and I think we can say by the 70s structured programming had won over the mindshare of most developers.
The second is object-oriented programming, which started in the late 60s, and achieved mindshare in the mid-80s to early 90s. The most influential OO language is Smalltalk (1983).
The third is functional programming, which either started in the 50s or the 70s (depending on where you draw the line) and is in the process of winning mindshare from OO right now. It's important to note that the modern incarnation of functional programming (by which I mean static types + abstractions like monads and applicatives) is very new, in programming language terms. The main standard for Haskell is 1998, Scala was created in 2005, and key techniques such as free monads are the subject of ongoing research. It has only been viable to do production quality statically typed FP for about a decade.
Python was created in the early 90s, and looks back to Smalltalk for inspiration. Culturally Python is (obviously) in the OO camp. Culturally, most developers are in the OO camp as well. Thus their definition of "good" is OO, and Python fits this. The linked post is a sign of the change that is happening right now---more developers are discovering FP and their definition of good is switching from OO to FP. From the perspective of an FP proponent, Python is not a good language.
The key point I'm trying to make is that programming is a cultural movement. We like to pretend we're objective and scientific, but we aren't. That's ok. Neither is science (see, e.g., "The Structure of Scientific Revolutions"). I do believe, however, that we're improving over time (said as a true FP adherent, of course :-)
I was under the impression that lisp was the mother of all functional languages, so I was surprised not to see it in your comment.
However, modern FP languages always start from some form of typed lambda calculus, because it has more desirable properties.
If anything, Smalltalk is the closest to "everything is an object". It doesn't even have an "if" statement, it's just a method call. Even there, the problem of mutation is present.
Most of the time my "objects" are just tuples, lists and dictionaries, with free functions to manipulate them.
In general, I dislike attaching complex semantics to types because that makes code more difficult to read. When you look at a function call and can't tell where it's leading by just looking at the code, there's an instant loss in productivity. At worst, you have to open the debugger and step through the code. This is particularly annoying when the problem domain requires no such abstractions, but they're in place just to shave a few lines of code.
How is procedural code pure functional? Can you explain please?
And how is procedural code supposed to solve the global/shared state problem?
I think the mathematicians got it right with typed functional programming, which has a rigorous basis. I think OOP is flawed because it's not formal enough.
I think this may have been the original intention of real OOP languages (OO theory itself developed as a way of modelling knowledge from human perspective [1]) such as Smalltalk but as the author says it just isn't feasible to do this "all the way down". In my experience OOP of this style is best used as a high level abstraction but beneath that for the sake of pragmatism and performance you usual need to get a bit more imperative.
The other perspective on OOP is that one models your solution space rather than your problem domain. One models data such as `CarHoodDrawingAlgorithm` rather than `CarHood` - and it's this reasoning that can lead us towards functional programming, which may be described as "OO for functions".
OO is still useful, because objects are something we naturally understand and can discuss. "Oh I got an exception when I called 'draw' on CarHoodDrawingAlgorithm / Oh really, that's a subclass of CanvasDrawer which I wouldn't have expected to throw that type of exception"
I think it breaks down where you have a mismatch between what different parties understand what an object to be, and how pervasive the paradigm needs to be. Quite rightly advanced programming topics are moving towards FP which more specifically answers the concerns in the solution space, which isn't I believe what OO was meant for.
[1] https://en.wikibooks.org/wiki/Cognitive_Psychology_and_Cogni...
OOP is a programming paradigm among others, the same way you have many types of hammers.
Only ignorance (or lack of culture) motivates oneself to use a mace instead of a carpenter's hammer for driving nails into a wall.
In case you're curious: https://en.wikipedia.org/wiki/Hammer https://en.wikipedia.org/wiki/Programming_paradigm
It's good to read from the OP and in this thread yet more explanations of what OOP was supposed to be about. Okay.
So, in my current project, I make a lot of use of the classes in .NET and have defined some classes with some methods, etc. Okay -- the encapsulation seems to help make the code easier to understand and debug.
I have made some use of polymorphism, and mostly I like it. So, I use interfaces, and, really, those are a lot like, but much less powerful than, what I used to do in languages where I passed as an argument to a subroutine or function the name of a subroutine or function the subroutine or function could call. E.g., in solving an initial value problem for an ordinary differential equation, have a function that does that and pass to the function a function that will evaluate the differential equation. For finding the minimum value of a function X, have a function Y that is a general purpose function minimizer and pass X to Y and let Y do the work of finding the minimum value of X.
For inheritance, .NET seems to use a lot of it, at least in their documentation, but so far in the code I've written I've never used any of inheritance. I love hierarchical file systems and the idea of inheritances of capabilities and access control lists (ACLs), but I don't really like the idea of inheritance among OOP classes. Sorry 'bout that.
If I want to build an outhouse, then I don't want to inherit a small house and add a toilet -- instead, I just want an outhouse.
For passing messages, sure, I saw that in Smalltalk but never thought to use it among objects in one program.
My server farm architecture and software does have some message passing among some of the back end servers. For a case of that, sure, OOP helps: For the data to be passed, I define a class, allocate an instance, put data into the members of the instance, serialize the instance to a byte string, send the byte string via just old TCP/IP sockets, wait for the response, a byte string, deserialize the byte string to an instance of the class that is supposed to get the returned data, and continue on. If there is an error, then the code writes a message to the Web site log file and returns a notification of an error to the user. Works fine.
For immutable state, no thanks -- e.g., in software about a car, the current state is position and velocity, and as the car moves that state changes and is not immutable. The speedometer of the car does not have immutable state, either, nor the steering, throttle setting, transmission gear, etc. Sure, could define an immutable state separately for each instance of time, but I guess that speedometer makers didn't think of that. If I put some raw meat in the oven and cook it, what comes out should be a good roast, and not something immutable. I think of state as changing, not as immutable.
Some of what the OP describes that is supposed to be real OOP, I was never tempted! No thanks! I never tried that and never would. To me, those rules were obviously nonsense, like programming standing on my head. Able to do it? Yes. Want to do it? No. A good idea anyway? Nope. Good to read that author of the OP now agrees!
But that de/serialization to/from a byte string, I like that! The .NET classes, sure, they are very nice to have. For sending data from one server to another, sure, define a class, allocate an instance, assign values to the members, and send the data -- terrific, easy to understand, works great. E.g, the data I send might be fairly complicated, but classes usually have enough generality that can define good places for all the complicated data.
And, can have arrays of object instances -- terrific! And one of the members of a class can be an instance of another class -- I do some of that.
And the methods have some name scoping,...
PS: I wanted to write "programming is not sacred", but I couldn't. I only use emacs and open source software. I love computers religiously and think that there is something magical about them. So I admit to a certain extent of hypocrisy. We computer geeks are obsessive people.
Yes, I saw some sense in some of the criticism of the GOTO statement, but, yes, I still use the GOTO statement.
Main usage: In a function, some bad situation is detected. Okay, that code might write to a log file and should set a return code value. Then that code just does
GOTO OUT
which means, time to give and just get out'a here, ASAP, where OUT is a statement label of some code that does whatever general clean up is needed and just returns.
There's another cute use of nearly a GOTO: For a loop, have something like DO FOREVER and in the code of the loop, maybe several places, decide when to get out'a the loop and then, do so with a statement LEAVE or some such which is really a GOTO the next statement after the end of the loop. It's essentially a GOTO and in some languages is implemented with an actual GOTO.
But there was a totally sweetheart use of a GOTO in PL/I: In some code could have a statement
ON FUBAR
where FUBAR was a condition that could be raised, and this statement ON was followed by some code to be executed if condition FUBAR was raised. The ON statement made the condition FUBAR enabled and established what to do if the condition was raised. The code of the ON statement could have a GOTO.
So, the code of function A has such an ON statement. Function A is called and the execution comes to the ON statement. Now the condition FUBAR is enabled and that ON unit is established for condition FUBAR should it be raised. If the code of function A returns, then that ON condition is no longer established.
Function A calls function B which raises condition FUBAR. Then the code immediately jumps to the code of ON FUBAR. If that code executes a GOTO to a label in the code of function A, then all the code in the stack of active code from function A and lower is ended (e.g., storage automatically allocated is freed), and execution continues.
So, look, Ma, a way to handle exceptional conditions that is easy to code and understand and does the usual, obvious stuff to clean up the mess for, e.g., no memory leaks.
And could do GOTO X, and the X could be a statement label in any code so far called but not yet returned, and the label in the code most recently called but not yet returned would be the target of the GOTO. Again would get stack cleanup. Nice.
Ah, Dijkstra would roll over, screaming.
GOTOs are a common part of life: If get a flat tire, then raise an exceptional condition, cancel a lot of plans and work in progress, and go to the shop of the towing service and call a cab. If the computer processor fan stops and the processor overheats, then something similar. Lots of common situations in life.
Actually, when this happens it only makes sense for you to get inspired from the real world again and end up designing a hierarchy called "bureaucracy"! </sarcasm>
- It starts out as a good and useful idea for certain problems.
- Then some people elevate it to the solve-it-all method and if you do anything else you are a bad person. Almost like a religion
- After a while nobody remembers why this thing has been invented and people just do it because they have to without any understanding why
I have seen this happen to:
- OOP (works great for a lot use cases if used in moderation but not all)
- Flat design
- Scrum/agile
- MVC
- Javascript
- Goto
- Loops (everything has to be a closure)
Right now functional programming is cool. Just wait until the people who have messed up OOP get onto the FP bandwagon. Clueless people will start doing FP and things will go to hell.
In the end you need to know what you are doing and why. There is no magic methodology that will save you from that.
ManagerManager managerManager = new ManagerManager