Goodbye, Object Oriented Programming
medium.com
medium.com
The problem is, sometimes you agree with only a small part of the platform. None of these things individually are terrible ideas if tastefully applied, but it all gets clumped together into one big blob of "the right way to do things" (aka object oriented programming). I blame languages like Java for selling certain ideas as The Right Way, and building walls that intentionally prevent you from using other techniques from different schools of thought ("everything is an object, no you can't write a function outside of a class").
I think the functional paradigm has a lot of good ideas too, but in my experience they're just as annoying if they're strictly and tastelessly applied in the same way OOP principles often are.
Don't be a "functional programmer", just take the ideas that are useful.
I tend to prefer languages and tools that adopt good ideas without promoting a single specific way of thinking.
I feel the same way about this as I do platforms. There is amazing projects on every platform. There is horrendous projects on every platform.
Taking sides just a narrows your point of view and lessens the amount you can learn.
That's the kind of 'taking sides' I was intending to articulate.
Most functional languages even give you ways of modelling imperative code (e.g. monads) in a way which hardly sacrifices expressiveness.
The real problem with OOP is not that it forces you to do things a particular way, it's that what's new about it (privileging the first argument of each procedure, inheritance, lots of hidden mutable state) is bad, and what's good about it (encapsulation, polymorphism) is not new.
For example I would guess that in a typical enterprise (say, a manufacturing company), 0.0001% of the software is written in a pure functional language.
Things OO languages 'force' us seem to be relatively easy to use: sending a message to a certain object, thinking in categories (elephants, houses, chairs, moveable objects, ...), state (position, speed, size, shape),... Thus it is not surprising that the overwhelming majority of software written in the last 10 years has been written in OO languages.
If we can do it, so can everyone else ;)
I'm the only one who came in knowing pure FP at all and I didn't learn it via classes.
Students there have more FP experience than most.
For example, the ruby library I use to connect to webservices is very OO, and requires you to derive from its base class to build each webservice client.
This is of course very natural in Ruby, but I do see lots of OO in Scala libraries too (e.g. Spray).
Agreed. This makes it a pretty easy sell to get everyone to commit to using immutability and purity and all those nice things in the business logic layer.
> Where things start to get trickier is the boilerplate and boundaries, e.g. I'm writing a Rails app at the moment where the business logic is in pure functions, but the boundaries with other systems like ActiveRecord and external webservices tends to end up being more object-oriented.
We're in the same boat, except in an in-house Java-based service framework. The "shell" of the program reads from the DB/makes service calls to gather input, passes this input to the pure business logic layer, and then based on the output throws exceptions, writes to the DB, makes other service calls, etc. This conscious division between effects and logic alone makes programs better.
In areas where we need concurrency or have more complicated effects we wish to unit test, we have started to use scalaz.concurrent.Task and scalaz.Free.
"Prove it" is our first line of defense. By always writing total functions, we can be confident that our services and jobs don't fail in uncontrolled ways. "Proving of theorems" is what you end up doing as you write total functions. NonEmptyList, ADTs, Options, Coproducts, These, singleton types, sized collections. All these things can provide powerful proof that is used to write total functions. The proof that various types provide makes it easier to write total functions as we can use Scala's powerful type system to greatly restrict which programs we even have to think about. And (thinking in reverse), forcing yourself to write pure, total programs ends up forcing you to prove various things about your program in order to achieve those properties.
So all in all, we perform the "proving of theorems" all the time. It just doesn't look or feel like it. But a large portion of CR discussion is about the types used in a program, which is in effect a discussion about which theorems we think we should prove before we push the code to production.
I do agree with you that thinking in FP takes a bit more experience however.
Eg when I asks students to do some simple exercise like "write a function that finds the shortest path through a given graph", in impure languages their solutions tend to return a path if they find one, but just give up with eg a message printed to screen when there's no path.
In pure and types languages, I see more people reaching for a Maybe or Either return type to express that the function as asked for might not succeed.
No doubt there ways to do it in a pure functional language -- but that requires a bit of thought. Compared to that difficulty, this business of return values seems trivial.
Worse, a language that forces people to Do The Right thing, might be acceptable for experienced programmers who want to work within that limitation. But when given to student, it just encourages closed minded rule-following.
Citation needed. I find the safety net of that kind of language empowering: I can concentrate on thinking about the actual problem, and be confident that the language won't let me shoot myself in the foot.
No problem. Traditional loops with mutable variables translate straight-forward to tail recursive calls, if you want to write your algorithm like that.
Though actually my argument was less about not mutating your functions internal variables---but about interacting with the world outside your function via a well defined interface. (In this case, via arguments and returned values.)
The most 'mainstream' language that encourages such a strong adherence to declared interfaces is Haskell. But the imperative D can do something similar:
"In a slightly less precise way, this means that pure functions always have the same effect and/or return the same result for a given set of arguments. As a consequence, a pure function for example cannot call other impure functions, or perform any kind of I/O (in the classical sense)."
I think experience shows that freedom to do the wrong thing frequently leads to the wrong thing happening. There are many case studies to be found in security and concurrency.
Also, they seem to grasp recursion much better via Haskell than in Python or Java. I believe that's mostly because Haskell makes structural recursion real easy (ie recursion along the structure of your data type, eg like along a tree)---and once they are comfortable with the basic idea from that, other patterns of recursion are easier to understand.
Algebraic datatypes and pattern matching on them are a great benefit for that structural recursion.
On the other hand, if you want to make an interactive application in excel you use VBA which exposes a large and rich set of objects.
Please don't encourage this. Because honestly debugging VBA written by consultants has definitely shortened by lifespan by a couple years
Simon Peyton Jones once did some work on giving Excel more programmatic power without having to go to VBA.
http://research.microsoft.com/en-us/um/people/simonpj/Papers...
Excel is very interactive: you can change input and immediately get a different output. (I think you were trying to talk about a different restriction. Excel is very limited in the kinds of inputs and outputs you can make, and even more so in the kind of side-effects you can cause.)
Whether or not you want your language to enforce segregating (most) effects is a separate issue, about which reasonable people can disagree.
I think pure functional programming is super useful in other contexts, for instance, a compiler. I'm weary of people suggesting that a specific school of thought is universally applicable, though.
A vertex shader just transforms an input vertex (and some other inputs) into an output vertex, and a fragment shader just transforms an input fragment (and some other inputs) into an output fragment. This is done at a scale so massively parallel that we have dedicated hardware for it, because your CPU doesn't have enough parallelism.
Sure, HLSL and GLSL are locally procedural. The most recent fancy versions ofo them even allow for some shared state mutation within a thread wave, useful for certain niche uses. But in in their traditional contexts? I find even local mutation for most use cases to merely be a source of bugs and obfuscation via poor naming. And most of the built-in functions are pure.
Just wanted to add that shaders lend themselves very well to the functional / dataflow paradigm. There are numerous visual node-based shader editing tools where you construct shaders from blocks, which essentially are pure functions.
And how often does that occur in practice for most of the programs people write? Even the most hardcore Haskell programmer isn't going around proving theorems about their modules beyond what the type system can provide for free (and that is true of statically typed OO also).
> The real problem with OOP is not that it forces you to do things a particular way, it's that what's new about it (privileging the first argument of each procedure, inheritance, lots of hidden mutable state) is bad, and what's good about it (encapsulation, polymorphism) is not new.
Encapsulated state has its own benefits and drawbacks (having explicit unencapsulated state limits reuse as they are reflected in type signatures), and anyways, is not unique to OOP (any commonly used language sans Haskell supports encapsulated state). The only way to get encapsulated state in Haskell is World -> World, which would pollute all function signatures it buried through (there is some good work in parametric effect systems, but its still early).
OOP also is not very new, being formed from the culmination have patterns used in the 70s, and is about the same vintage as FP.
The simplest case is replacing an expression with its value, given an environment of lexical bindings that are apprent from the source program. Not much investigation necessary. That's FP.
For OO code, you just need to keep track of a lot more context: the state of the receiver of the currently executing method, its heirarchy of parent classes, the runtime class of each object because late binding is pervasive. Of course you can write code that doesn't use any OO features, but the languages clearly aren't designed for it. See: any number of "functional C++" articles.
And, of course, you can get the same kind of highly dynamic behavior in FP languages by explicitly using open recursion, higher-order state and hiding everything behind existentials. But very few codebases do that because the vast, vast majority of the time just one of these features is enough to solve a problem.
OO thinking optimizes for the less ideal cases that are far more common. Ideal FP has this ideal mathematical view of the world that rarely pans out in practice, while less ideal FP just resorts to OO-style metaphors and abstraction in practice. For the kinds of experiences I work on (heavily reactive, lots of state, complex interactions), this works well for me, and we are developing techniques to make it less painful (live programming). "Worse is better", as RPG would say.
Why should expressing something basic like a tagged union require a detour through the quirks of a particular object system? Ditto for polymorphism, modularity, etc, etc. Clearly we disagree on how useful objects really are in practice, fine. Why not add these domain-specific features on top of a core language with simple semantics? It worked fine for lisps. Luckily, after a couple of decades of "everything is an object!" nonsense, that seems to be where newer languages (Rust, Swift) are headed.
Languages are not so much a collection of features but mindsets. So polymorphism, dynamic dispatch, subtyping, etc...do not define OOP so much as they are leveraged by those languages to enable reasoning with names and metaphors. Calling them just domain specific features misses the point like talking about some dish only as the sum of its ingredients.
Tagged unions and GADTs are quite different in expressiveness and modularity, I still remember the mega case matches used in scalac. Should one function really be given so much functionality when a layered design with several virtual method implementations would be much more amenable to change and modular reasoning? Well, I guess it's a matter of how you view code.
Languages are not so much a collection of features ...
If you want to define objects precisely, even just to have a language spec, they are absolutely made up of sums, products, recursive types, etc. Whatever useful metaphors one might have to work with objects doesn't change what they actually are. If you give the programmer access to these building blocks, you get ML. Calling them just domain specific ...
I meant that the particular way they're combined to get OOP is domain-specific.Again, I'm just asking: why mess with the basics? Why require encoding simple, universal concepts in terms of an ad-hoc object system? You can still have an object system on top, if you find that helps with the really complicated cases, but why encode the parts of your code that are simple in terms of much more complicated, derived concepts?
Thinking is probably what got you the bug in the first place, so to kill it a different angle is usually preferable.
In practice, this means sending in test data to see what happens and/or stepping through the code to see where it diverges from what we think it does.
But to flip it around and make a less grandiose version of the same point: the majority of bugs are to do with state. (I don't actually know if that has been "proved" but it's definitely what I find in practice!) Pure FP gets rid of a whole swathe of potential bugs at one stroke. Of course it makes programming harder in some other respects, as often it can be hard to figure out a pure function that's as efficient as the obvious imperative algorithm, or even to figure out how do a state-y thing in FP in the first place.
And we organize our programs such as to get nice properties.
"What the type system can provide for free" isn't static. Part of working in that kind of language is structuring your code such that important properties end up being proved by the type system.
It rarely rises to the level of a "theorem", but "is this piece of code equivalent to this other piece of code?" is what I'd venture to suggest programmers spend most of their day asking, and Haskell-like languages make that easier to answer.
It is a boring question to ask, and one that doesn't come up much in my work. Does this mean Haskell-like languages are not appropriate for my domain?
For me programming is about automating some business process or at least something you already know how to do, so it's a case of: 1) transcribe the doing into plain code 2) factor out patterns/redundancy in the code. (In practice this is interleaved, and 2) results in building up a language for expressing the domain in code which in turn simplifies 1) - indeed, when adding new functionality it's often a case of first transforming the current code into a representation that makes the new functionality simple). 2) is where the vast majority of the work lies and what takes up the time. Just as good writing is good editing, good coding is good refactoring. At least in my domain.
Very frequently. The simple theorems that equational reasoning supports are very convenient for refactoring and optimizing code. Understanding components in isolation is useful for testing, isolating bugs, and refactoring of subprograms independently from their contexts. Since I do testing and refactoring frequently, I make good use of what FP offers.
Sure, this isn't equivalent to full 'correctness' proofs. The lightweight equational reasoning FP offers for expressions and behavior doesn't prove you have 'correct' expressions or behavior. But it doesn't take theorems of correctness to be useful.
> that is true of statically typed OO also
Not really. Statically typed OO doesn't prove nearly as much about the behavior. This is mostly due to potential for aliasing of stateful sub-components. If you have capability secure OO, like E language or NewSpeak or Joe-E, that can help a lot, but the potential for hidden communication via aliased components still hinders a lot of useful equational reasoning.
> only way to get encapsulated state in Haskell is World -> World
That's simply untrue. Encapsulated state in Haskell can be modeled by a variety of types. One is `Machine i o = i → (o, Machine i o)`. Such machines can be composed, each component machine having its own internal, encapsulated state.
> OOP also is not very new, being formed from the culmination have patterns used in the 70s, and is about the same vintage as FP.
I'd say pure FP is a lot younger. The idioms for purely functional expression of IO simply didn't exist in the 70s. E.g. even in the mid 80s, you had languages like Miranda with impure 'readFile :: FileName → Bytes'. The Clean programming language using uniqueness types for IO was a new thing in the mid 80s. The modern use of monads harkens to Wadler in the mid 90s. I tend to think of `pure FP` as more a mid-90s paradigm. It really is a different paradigm - a different way of thinking about and constructing programs - than the ad-hoc impure FP of Lisp and OCaml.
I was generalizing across all programmers. Of course individual cases may vary, and it really depends on the code that you write!
> Not really. Statically typed OO doesn't prove nearly as much about the behavior. This is mostly due to potential for aliasing of stateful sub-components.
Haskell simply avoids aliasing with value semantics, and even then you can add it back and get the same problems.
> That's simply untrue. Encapsulated state in Haskell can be modeled by a variety of types. One is `Machine i o = i → (o, Machine i o)`. Such machines can be composed, each component machine having its own internal, encapsulated state.
I'm not disagreeing. My point is that you have to bury the state in something else, which is then exposed via a signature (well, in Haskell which supports this, of course).
> I'd say pure FP is a lot younger.
We could debate a lot on what pure FP is, what pure OO is, and who climbed the ladder faster. But the fact that paradigms developed around the same time is probably due to the programming field maturing in that time frame.
I think the relevant question is whether functional programmers, not all programmers, regularly leverage the lightweight equational reasoning, refactoring, and context-independent behavior that is available with purely functional programming. I believe most do.
> avoids aliasing with value semantics, and even then you can add it back and get the same problems (...) you have to bury the state in something else, which is then exposed via a signature
In a programming model without side-effects, it is necessarily the case that all 'effects' are modeled in the call-return behavior. The type signature, too, for a strongly typed language. And I agree that, upon modeling state or aliasing, we get to deal with not just the feature but all of its associated problems.
OTOH, the problems of state and aliasing don't implicitly leak into every subprogram. The precise control over effects can be very convenient.
You couch that control in pessimistic terms like "pollute all function signatures it buried through". But in practice I've never had difficulty anticipating where I'll need to model effectful behavior, nor with extending the set of effects as needed. Oleg's recent development of freer monads with more extensible effects [1] is compelling in its potential to remove remaining drudge-work.
[1] http://okmij.org/ftp/Haskell/extensible/more.pdf
> We could debate a lot on what pure FP is, what pure OO is (...) paradigms developed around the same time
Pure FP (programming with mathematical functions, no side-effects) and impure FP (i.e. first-class procedural programming) are essentially different paradigms. They require different patterns of thinking, reasoning about, and constructing programs. Despite the ongoing battle over the "functional programming" branding, it isn't wise to conflate the two paradigms. It was impure FP that developed around the same time as OOP. Pure FP is about twenty years younger, more or less.
(The mention of 'pure OO' seems an irrelevant distraction. Do you believe pure OO vs. impure OO, however you distinguish them, require significantly different design and development patterns and are thus distinct paradigms?)
This begs the question of who is a functional programmer, and how typical are they? I know a few FP programmers who are able to stay in the abstract world for a long time, thinking symbolically, equationally, and don't need petty things like concrete examples (most of the members of WGFP, for example). Then there is the rest of us!
> In a programming model without side-effects, it is necessarily the case that all 'effects' are modeled in the call-return behavior. The type signature, too, for a strongly typed language.
You can always default to World -> World in a pure language, and ya...technically you don't have side effects anymore, but for all practical purposes you do! For this to be useful at all, you have to keep your effects fine grained, and for functions that call other functions (like a general ForEach), effects have to be parametric as well, or you wind up polluting everything (or worse, being unable to express something).
Pure FP culminates from a bunch of experience in the 70s (I'm not talking about Lisp), which happens to be where OO came from as well. Pure OO doesn't really make sense...OO can't be pure but rather organic.
A pure `World→World` effects model is NOT a practical equivalent to introducing side-effects. Unlike with side-effects, it's trivial to constrain a subprogram from access to the whole World, e.g. use divide and conquer tactics to hide parts of the world, or limit a subprogram to a constrained subset of monadic commands that an interpreter uses to access and update the world in limited ways.
No experience from the 70s left academics or industry prepared to support purely functional IO models. It should go without saying that you can't have a complete programming paradigm without an effective story for IO. Miranda, an attempt at a purely functional language of the mid 80s, made a valiant attempt at pure IO, and failed. Even early Haskell, up through the early 90s lacked an effective, coherent story for IO. I find your repeated assertions that pure FP was somehow a child of the 70s to be very dubious.
IF you add a World->World effect to your function, it is the equivalent to saying it has unconstrained side effects, is it not? It is the practical equivalent, even if its implementation can pair off the world as needed.
Hindley Milner, Landin, ISWIM, APL, list comprehensions, etc...are all children of the 70s. The IO problem wasn't solved until Wadler in early 90s, but then again we didn't get type parameters in OO languages before then either.
A World->World function is unconstrained in its effects upon the given world, modulo substructural types. But it is not a "side effect". Among other important differences, your program can't describe an infinite loop with observable effects on that World.
I don't consider type safety, list comprehensions, etc. to be essential for pure FP. Immutable values, first class functions, and explicit effects models on the call-return path (instead of side effects) are the critical ingredients. Comparing the essential IO problem to "type parameters" (which aren't even a problem for a rich ecosystem of dynamically typed OO) seems disingenuous.
See "Pure Lisp", a style and subset, where no side-effects has been used.
How much of the Haskell (a pure FP language) library code is formally proved? Just curious.
Also I wonder, if code like xmonad can be proved at all.
One might consider proving the properties instead of just checking them with a few thousand random examples.
I believe you're referring to a type system. That is different. There is little about functional programming that allows you to easily prove theorems about your code.
I seriously doubt there's much industrial code made in functional languages that has been proven to be correct. And I'm not talking about halting problem, etc., not that deep, I mean just writing a spec of what the code would do in every situation and proving that indeed it does that. I haven't seen any. Maybe in theory it'd be possible, in practice, it doesn't happen. In fact, if that happened frequently, unit testing in functional environments would not exist - you don't need to test what you can prove to be correct. In reality, it very well exists.
> There is a reason why the majority of proof assistants are implemented as functional languages.
It's not the direction you need to prove though. Surely, it is easier to express things we can prove in functional terms. The challenge, however, is to see if it's easy to prove that code follows certain specs when that spec is not specially fit for being expressed in functional terms.
> it's that what's new about it (privileging the first argument of each procedure, inheritance, lots of hidden mutable state) is bad
Hidden mutable state is not mandated by OOP in any way. And arguing that inheritance and special arguments are bad would require some more than just saying "it's bad".
> and what's good about it (encapsulation, polymorphism) is not new
So what? Where's the problem if it's not new? Letters we're using aren't new, so aren't numbers, still serve us well. Not everything good must be new, if it's old and still good - even better. I fail to see how "it's not new" is any meaningful criticism - it's like criticizing a math library that the result it returns for 2+2 is not new. Why would we want it to be new?
That's only true for certain values of "real". In real "real" terms, the advantage is purely virtual - even very simple programs require hundreds of pages of proof. In effect, formal proofs are almost never done in real reality and the "advantage" remains purely virtual.
> what's new about it (privileging the first argument of each procedure
Multimethods, CLOS, Dylan?
> inheritance
Don't use it if you don't like it that much...
> lots of hidden mutable state
It's not hidden, it's encapsulated. Do you know Smalltalk? If not, you should try learning it; I liked Pharo very much. Look at Morphic, work with it for a while and you'll see the difference. You can also try devising purely functional equivalent of Morphic while you're at it (good luck!).
As you mention, pure FP does require learning different idioms. OTOH, a lot of those idioms can be applied effectively to imperative programming, so learning isn't necessarily a wasted effort even if you spend most of your time wrist deep in C code.
Do you mean promoting, or enforcing?
There are many aspects of, say, the Zen of Python that I am very grateful for.
The reason why an OOP language doesn't let you have functions outside a class, may be the same with why you don't have blades on a nunchaku. You may hurt yourself.
One needs to understand the strenghts and weaknesess of each weapon, and when it's better to use it.
Slave obeys. Master chooses.
But it's not about taking. It's about giving. What interfaces do you provide to someone else? Are the datatypes that you define mutable? Do the functions that you expose behave statefully? Do they modify the outside world?
All of these things are things that functional programming (I guess I'm thinking of Haskell-alikes specifically) nudge you away from. Functional languages take power away from you to give power to someone else: the consumers of what you write (who may be youself in the future).
1. Inheritance creates dependencies on their parent class
2. Multiple inheritance is hard
3. Inheritance makes you vulnerable to changes in self-use
4. Hierarchies are awkward for expressing certain relationships
All true. But likewise, functions introduce dependencies on their arguments, and data structures introduces dependencies on their fields. You must consider your dependencies carefully when designing any software interface.
The task of software architecture is not to go around categorizing everything into a taxonomies. Inheritance is just one tool in your software interface toolbox.
5. Reference semantics may result in unexpected sharing
This has more to do with reference semantics than objects.
6. Interfaces achieve polymorphism without inheritance.
Interfaces long for inheritance-like features. For example, see Java 8's introduction of default methods, or the boilerplate involved in implemetning certain Haskell typeclasses.
His entire problem seems to be he thought OO was a magic bullet he could do whatever he wanted with and then he learned there was more to using OO than the three concepts he cites at the beginning.
And this guy has supposedly been writing in OO languages for decades? What?
For compact, a "good" program converts the expression of intent (the source code) into the execution (executable) into a package of data in which both statically (being loaded, being resident) "good" is defined by the minimum use of resources, and dynamically (while running) it minimizes its resource usage (and thus is "fast").
For correct, a "good" program works the same way every time you compile it and run it. Good in this case would be systems which notify you of changes which can affect the existing compiled code.
And for "uniform" the same syntax or tools are applied in the same way for all idioms. "Bad" here is the number of special cases that have to be accounted for (or inversely good is the lack of special cases).
As he wrote, the "promise" of the object oriented approach to programming has not been fulfilled according to a set of criteria that the author came up with internally. That sums to an opinion of dislike based on an internal metric.
That said, since I share what seem to be his unidentified metrics for "goodness" I found myself agreeing with his statements. I recognize though that there are many metrics which others may choose to rank more highly than those so rather than "failed promise" I'd characterize it as "It hasn't worked out to provide a better solution for me."
This is the bit i don't get. It's like he learned OOP in the '90s, when everyone thought inheritance was rad and nobody had realised how terrible mutating shared state was, and then fell asleep for twenty years. None of this article, none of it, has any relevance to how OOP is practiced by informed people today.
Well, if he's been doing OOP for decades, isn't that likely to be the case?
Unfortunately, they seem intent on adding more mutable state rather then eliminating it.
Being encapsulated means you don't actually have any control over the logger, or the threads it spins up. Creating a logger for a new app requires inheriting from a application logger (which is already 3 or 4 layers deep in the inheritance hierarchy.
The distinction other loggers make between the log interface and the appenders are non-existent. If you want to log to a new source (say the event log) you have to add a new layer to the inheritance tree.
Then there's the fact that it's handling the application state, in an "on error resume next" kind of way. And this is a global state, so don't even think about multi threading.
Naturally, actually accessing the logger is done via a singleton.
It causes more problems than it helps resolve, and the ones it causes naturally don't have an diagnostic information available.
There are plenty of others, but it's hard to top this tour de force of OO anti patterns. If only there was some free and stable alternative...
There's no shortage of good-enough loggers out there for major languages.
I think point of this article is OOP have so many limitations for safe usage, that makes usage of FP more comfortable.
1. You depend on the library and its dependencies
2. Version controlling libraries is hard when you depend on A and B both of which depend on C
3. Someone changes something in, say, npm and it breaks everything that depends on it. Also, security is only as strong as its weakest library.
4. Do you put everything in one monolithic library or have libraries require each other? Requiring is also a hierarchical graph.
5. Libraries may want to share pointers to the same objects, but then who owns them?
6. Libraries do things for you but sometimes you need to override or customize something, so that breaks encapsulation.
And so forth.
It needn't be. The awkwardness of multiple inheritance in C++ comes from the fact that C++ classes try to be both classes and abstract data types, and end up not fulfilling either role in a satisfactory manner. In OCaml, where abstract data types and classes are completely separate and unrelated mechanisms, multiple inheritance is the most natural thing in the world.
> 4. Hierarchies are awkward for expressing certain relationships
Hierarchies are totally fine. The problem is tying vtables to individual objects. This deprives you of the ability to say “this virtual method operates on two objects of unknown types”, because the only type that can be abstracted is that of the object carrying the vtable. Haskell-style type classes and CLOS-style generic methods don't have this problem.
> 5. Reference semantics may result in unexpected sharing
Unexpected sharing is only a problem with mutable data.
> This has more to do with reference semantics than objects.
How many languages that call themselves “object-oriented” don't equip all objects with a first-class identity?
> This has more to do with reference semantics than objects.
I would argue that these are intertwined. You can't really have objects without aliased identity and therefore reference semantics. Otherwise, you are in "land of the anonymous values" where FP techniques are much more useful, while objects clearly have names. (I'm an OOP fan BTW, but also heavily use FP.)
I would also argue that too much elegance in any language or design paradigm faces the same problem, in that elegance is often (but not completely) in tension with modularity or granularity of control, which is an engineering subgoal, because a common engineering situation is to experience system change (feature growth or reorganization of code), and improvements in granularity or modularity makes system restructuring easier.
I think that object oriented design is more essentially about modelling distributed state, because you probably have multiple objects with their own internal state. I believe this means that object oriented design is highly concerned with protocolized communication and synchronization between distributed states, whether via messages or channels or something else.
I believe that in distributed situations, object oriented design can be very harmonious with functional reactive programming strategy. You can easily and usefully have a situation where an object functionally updates its internal state with a typed stream of inputs.
Functional came before OO and there are reasons why it became much more popular- it had much better, easier and simpler solution to the most common problems of the 90's and early 2000's, namely handling GUI and keeping single process app state (usually for a desktop app).
It fares much worse in today's world of SaaS and massive parallel computing.
Frankly I think the discussion will be much better if we debate the merrits of each paradigm in the problem domain you are facing, rather then blindly bashing on a paradigm that is less suited to your problem domain.
For instance I have yet to see an easy and simple to use (and as such maintainable) functional widget and gui library.
Like react?
Or possibly FOO.
> every component must be created by extending a base class
This is an implementation detail, instead of ES6 classes you can also use React.createClass(), which might as well be named React.createComponent(). These "classes" can't be inherited from.
<test_component/>
Buy even if you could write every component with the stateless style it doesn't change the fact that your framework/GUI-library is using OO extensively and in it's fundamental concepts, I think that makes it OO, you seem to disagree.
From the React docs:
> In an ideal world, most of your components would be stateless functions... This is the recommended pattern, when possible.
I will continue to shill for Elm[1], like I always do.
Isn't that the point? Since Elm is a pure, strongly typed language, it introduces a lot of constraints. You can't mutate anything, there are no side effects (only declared effects captured by the type system), and the only architecture you can use is "The Elm Architecture". However, those restrictions will keep your code sane and the compiler almost guarantees that your code will work. It's a pretty fair trade-off if you ask me.
But, it is not whether a paradigm/language is better than another one.
It is about getting shits done. The paradigm/language war/agile are cargo cult science. It is transforming a practice into a religious thinking while it is not the case.
All the list he makes about OOP is right, on the other hand, you can admit that objects as a namespace can be a right thing.
Like ... well the stringIO, the string and the file or IO objects. But, the data encapsulated these way have finite well known states.
I had a lot of pleasure using OOP in Perl, python, PHP because it was easy to remember where things were for some stuffs, and error/resource handling can be done in a nifty way, even though trying to make stuff optimized for speed or memory required to dive into the guts of the implementations or counter-intuitive so called intuitive ways of doing stuffs.
I guess the rant can be made about making leaky stateful objects that are coupled. Which is what OOP has been advocated for years now. With every dimension with finite states you couple, your cardinality is the cross product of every dimension and it grows more than linearly, defying very soon the brain cognitive power. But still, people insists on going for more complexity because their «tool» can handle it. The limit is NOT the language. It is the human brain. But the hubris is there.
Is Bill built from an Invoice itself built from a Quotation, or are these simple transition applied to a document through the use of functions?
The OOP approach may seem simpler for «encapsulation» but as soon as you add the polymorphism to add the payment gateway and the serialization of data ... and have to make it straight with book-keeping while no developers actually understand accounting, this becomes hell to synchronize what should be plain documents that are worthy with objects. Because ... coders are lost in complexity and lose focus about the real world application, and that what they think may be wrong. Very few coders I know check that their model fit the reality : they make assumptions.
The Quote/Bill/Invoice is basically no better as a poor document in ascii you can read and is self descriptive with stamps giving the stage.
The problem of OOP is documents are «abstracted» as a view on multiple states that are poorly modeled because people are overthinking the implementation and losing the goal : to put $ in the bank and make your accounting/billing/commercial services working. Very soon, the document is very dependent from a database, its view, its controler ....
And, while most boss are kind of embezzling. They don't say it this way, they say they want something flexible that can be easily changed. To make an innovation. All our IT is about subtly walking around rules or regulations.
Coder are over-specialized in coding, while they should first learn the craft they are modeling.
Other times, bosses are overgeneralizing expecting rules (like accounting) to stay the same across country. Well coders being believers they have bugs, not because their code is complex, sometimes because the real world accept no possible automation of tasks.
The problem sometimes do not lie in the paradigm or the language but in the excess of confidence people have.
Tk could be seen as a predecessor of DOM and CSS (as widget-building paradigm), possibly other widget libraries like GTK as well. In Tcl, OOP has evolved along other, if overlapping lines, as can be seen in the core oo::* namespace.
Advantages and disadvantages of various OO systems for Tcl have been discussed for a couple of decades. Issues around all of the aspects of OOP reviewed in the article were evident and the focus of disagreements, which showed the limitations of any particular approach and, as usual, that no free lunch or perfect answer can ever be found.
As the language was being further used for research at Xerox PARC and Genera.
OO maps nicely to shared memory model and it's fairly low overhead compared to things like persistent collections data structures. This helps when you are forced to deal with lower level stuff.
Nowdays we are IO bound, we moved from single shared memory machine -> distributed services communicatig with messages and the problems are more about data transformation rather than HW management. OO is cumbersome in this context.
Functional GUI is easy to do, look at clojure react wrappers.
OCaml is famously faster than C++.
benchmarksgame.alioth.debian.org/u64q/compare.php?lang=ocaml&lang2=gpp
But yes, Ocaml is quite fast. Which was my point.
Which is my point - there are languages that can compile to similar code as C/C++ if you write similar code - but the idiomatic code is still slower because it doesn't map to hardware cleanly and have inherent overhead, this is why functional languages didn't catch on when we were heavily CPU bound, it was common for people to write ASM because they couldn't get good enough perf out of C compilers.
Now that we transitioned from vertical to horizontal scaling problem domain maps very nicely to functional languages (actor model/message passing style communication is practically transparent with immutable data function calls).
I have worked with other functional languages (Clojure) and I have optimized GCed code and it mostly boils down to what I said - writing C/C++ like code in language X to get as close to hardware memory model (avoiding GC by pooling, increasing cache coherence, using value types or manually unfolding data structs, etc.)
I haven't seen a magical way for high level data structures based on trees (used for persistant strucutres) to outperform arrays
But yes, OCaml is quite fast, and probably could be faster. Does it really beat C++ in all contexts? Probably not, but it's still pretty fast.
I'm guessing you are talking about Lisp, but not only are Common Lisp (and the languages it evolved from) not at all functional in any technical sense of the word, many of them only had dynamic scoping, and often used mutation of data structures implicitly.
Stop talking nonsense.
EDIT: If you are talking about scheme, then the same exact points apply. On top of all of the above points, nobody in the world other than a few extreme language-philes ever use any of the functional languages, and for good reason. Functional programming is nonsense, the only people that talk about it are academics and people who want to seem smart. Functional != Structured Programming, Functional != Procedural, 'functional' has a precise meaning: treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data. Neither Schema nor Lisp nor any but a very few quite obscure languages satisfy this.
Lambda calculus is technically the first functional language, and predates computers. But if we're talking about actual programming languages, SASL, one of the earliest functional languages actually implemented, was released the same year as Smalltalk, and was influenced by the older ISWIM, which was never implemented.
>I'm guessing you are talking about Lisp, but not only are Common Lisp (and the languages it evolved from) not at all functional in any technical sense of the word.
The functional style (writing most of your code as pure functions, keeping impure code isolated), however, can be practiced in ANY language. It makes your systems easier to reason about, because you know if state is being modified. And Lisps had many constructs that made FP convenient. Like map. So while Lisp, on the whole, wasn't functional, it was one of the easiest languages to write functional code in for some time.
>On top of all of the above points, nobody in the world other than a few extreme language-philes ever use any of the functional languages, and for good reason.
On the contrary, FP has heavy use in finance and other places where there is a low tolerance for error, as well as Microsoft and many other conpanies. It has heavy use in programming language research, and a functional programming language was used for program analysis to aid in the optimization of FFTW, which is still one of the fastest implementations of the Fast Fourier Transform.
>Stop talking nonsense.
As the above demonstrates, you're the only one who's doing that.
Simula was working in like '67, which is like 5 years before smalltalk.
My point is that functional languages do not predate OO languages, and if anything come later, and you never really addressed that.
That's just the first one that was implemented. The IDEAS reach far further back. ISWIM was '66, it it's not the oldest.
>My point is that functional languages do not predate OO languages, and if anything come later, and you never really addressed that.
Between my points on Lisp, ISWIM, Lambda Calculus, and others, I think I did that quite adequately.
But your main point was that FP was useless, deriding it, and claiming that, "only people that talk about it are academics and people who want to seem smart," which is demonstrably not true.
Even Smalltalk had map, filter, etc, and HOFs.
Using your definition, virtually every language in use today is 'functional', all the way down to Fortran 77, since really any language you can get a pointer or other handle to a function or callable object would let you implement map, filter, etc.
Really the existence of map, filter, etc are not a clue to a language being 'functional'. Yes, you would need such things, but they are not sufficient. C is not a 'functional language', even though technically you can write C that is completely functional in style.
In fact, almost any modern language can be used in a 100% functional style, avoiding side effects and state completely. Nobody does this.
Even if it is one of your favorite things, virtually no one uses functional languages. This isn't because people are dumb, it's because functional languages are cumbersome and inefficient.
C lacks (last I looked) nested functions, closures, functions as first class data types, ...
> Really the existence of map, filter, etc are not a clue to a language being 'functional'.
'Functional Programming' isn't black and white. There are pure languages which ENFORCE functional programming like Haskell. It's their many paradigm, the language and its library make heavy use of FP and one has to actively manage side-effects.
Then there are language which SUPPORT some forms of functional programming, for example by providing a subset in the language and its libraries which can be used side-effect free, makes use of higher-order functions, etc.
At the bottom are languages which ENABLE some basic functional programming idioms. Though most of the language and its libraries are not using FP, the user can still use some FP constructs like nested functions, closures, etc.
Generally I agree, that pure FP is not that much used in the real world because of a lot actual problems (difficult to learn/use/maintain, efficiency might be achievable by experts, needs understanding of slightly advanced mathematical concepts, ...). There are some more or less used implementation languages of more or less pure FP (GHC, OCAML, F#, ..., Clojure) languages. I'd guess that the main applications are in domains where they have highly skilled people: finance, data analytics, verification/specification, ...
I sometimes see that companies advertise use of FP languages, but in reality that's marketing in recruiting to look cool and to attract clever people (who then have to work on Java jobs, mostly).
Where are the software projects that are built using functional languages?
I'll call out XMonad. What can you come up with? Obscure companies doing obscure things, or else people using Common Lisp in a nonfunctional way.
What major project or product is built using Haskel or Ocaml? There aren't any! The only thing I have ever used that were written in those languages are XMonad and csvtool respectively.
That doesn't seem like it would be a very useful definition then.
Common Lisp and its implementations supports some parts of Functional Programming:
* First Class Functions
* Higher Order Functions
* Anonymous Functions
* Lexical Binding
* Closures
* TCO is supported by a variety of implementations
Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He carefully read the entire "Lambda: The Ultimate..." series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress. On his next walk with Qc Na, Anton attempted to impress his master by saying "Master, I have diligently studied the matter, and now understand that objects are truly a poor man's closures." Qc Na responded by hitting Anton with his stick, saying "When will you learn? Closures are a poor man's object." At that moment, Anton became enlightened.
http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/m...
OO lets you abstract away a lot of detail, but locks you into some rigidity that doesn't map perfectly onto the real world. It's a leaky abstraction. But so is _everything_ real that we attempt to represent in a computer or in any formal system. Gödel proved this 85 years ago.
Code reuse is entirely possible with OO. The practical difficulties of code dependency management are not unique to OO. Anyone who's ever developed anything non-trivial in Node has seen how insane the dependency tree can get. Every language and platform has its own version of this problem and its own solution. From Windows DLL hell to Ubuntu Snap, from Bundler to Virtualenv, this problem transcends any particular style of programming.
It's good the author is skeptical of the promises of functional programming, but the total rejection of OO concepts as useless reveals that ultimately the author didn't really learn anything useful. The author fails to address how abandoning OO solves any of the problems he claims to have. "Ew, that's gross!" is not a useful analysis.
Then due to some directions I was taking at my job, it became very valuable to run millions of simulations of warehouse and transportation operations. After months of pain, I discovered object oriented programming (luckily I didn't have to abandon my language of choice to get it). Comparatively speaking, there wasn't a functional design pattern I could find that could come anywhere close to the simple elegance of OOP for modeling people, vehicles, warehouses, etc.
It's almost as if different ideas have different virtues in different domains.
That's interesting. I think simulation specifically is an area that fits OO too well. It even fits the classic texbook example of a "Car is a vehicle, which enapsulates and engine. Its state has speed and position. etc".
This approach doesn't mean no objects ever. But only when your problem actually calls for it. Likewise you will also end up with parts that are purely functional, data-oriented, etc. But they will be used where they make sense.
On top of that I'm also using pure C99. It does away with a lot of the fluff and cruft in other languages. In the past I used to try to fit my problems into whatever the most fancy language features I was offered. Which cost me a lot of time analysing. Now I just solve my problem.
Mind you, C is not a perfect language. There are features I wish it had. But for my approach to programming it is the most sensible to use. Apart from maybe a limited subset of C++ (such as function overloading and operator overloading for some math)
That is the same technique that Stephanov describes in 'From Mathematics to Generic Programming'.
1) Inheritance can be confusing and messy.
Yes, hence the advice: Prefer composition over inheritance. Instead of having B inherit from A, declare an interface I, and have both A and B implement I. If B wants to reuse A's functionality, it's free to do so through composition, and not through inheritance.
There are some edge cases where inheritance is vastly simpler than composition - mostly when the interface requires you to implement 20 different methods, and there's only 1 method that you really care about changing. Using inheritance here gets rid of a ton of boilerplate, but that's a conscious choice you're making. If you don't like this, just revert to using composition.
2) Encapsulations can leak if you write buggy code
Any program can break if you write buggy code. Not sure what the author's point here is. In order to encapsulate your class carefully, either accept immutable inputs, or make deep copies of them. If neither happens to work, warn users that class behavior is undefined if they misuse it. This is what every non-thread-safe class already does anyway: it warns users that if you use them in a concurrent manner, things may break.
More importantly, when dealing with internal state that's created by the class, make it private and ensure no one else can access it. This also serves to encapsulate the internal implementation and algorithm from external users.
3) Polymorphism is... not unique to OOP languages?
Yes, using interface-based polymorphism is a good idea, and covers most of what people need. How does this make the argument that we should never use OOP languages?
--------
The author brings up valid points about what to watch out for when coding in OOP. If you read other books like "Effective Java," they bring up the same points as well. But instead of acknowledging the benefits that come with OOP as well, and teaching people how to avoid these pitfalls and write code the right way, the author jumps to an extreme position that OOP languages should be abandoned entirely. Can we please avoid this type of wild overreaction, and pointless jumping from one shiny tool to the next, in a never-ending search for a silver bullet that will solve all of our problems. Because let's face facts: No such silver bullet exists.
I guess he does make a point about inheritance and how confusing it can be, but like you said OOP still has value outside of inheritance.
Personally I want to learn from and use a language that supports as many of the paradigms as possible, like Scala or Swift. Let me choose based on what I need. That being said I'd much rather work in pure FP then OOP because of the fact that most of the advantages of OOP can be achieved via FP and the inverse is not true.
Templates or generic programming can be argued to be another paradigm, and quite powerful as well.
D supports that as well, along with purity checking for FP.
Bob Nystrom wrote a very good chapter on composition in his Game Programming Patterns book [1] and is worth reading if you want to program in the OO paradigm.
Apple calls Swift a "protocol-oriented" programming language, and with the addition of first class value types, tries to solve these problems in their own way.
I'd definitely suggest people frustrated by the problems outlined in this post to check out the Apple talk on protocol-oriented programming in Swift.
If you're writing a Mac or iOS app, you're generally writing Cocoa with either Swift or ObjC syntax. Cocoa is unbearably stateful. For a simple app, you not only don't get to work with value types, you typically don't even get functions or methods which return anything.
Obviously for something more complex, your non-UI code can absolutely be written with the principles described in that video in mind, but if you try to apply those principles to Cocoa APIs directly, you're going to be fighting against its stateful nature constantly.
CoreAnimation and bindings make AppKit very slightly more functional, but not really.
They are also widely explored in languages that support some kind of polymorphism.
The problem is getting the common developer interested in making good use of them.
In 2016 we are still talking about Cobol, which is spread in a relatively niche market and considered as a pillar in fields like banking, how can the object oriented paradigm be considered " past or even bad? It is the present and will be the future for at least the next 20 years, considering the number of billions lines of code. From a management perspective, such statements are not strong enough to be justified.
I find this sort of articles to be just bread and butter for codemonkeys, people who learn the most recent paradigm, technology or whatsoever and think that it's the key to happiness, or people who read for the first time a book like the ones from Bob Martin and feel they already know how to develop good software - or poems, as mentioned somewhere in the book - and list the bad things about other types of software architecture or design or whatever.
Legacy code bases don't make a language good.
I personally could say goodbye to Assembly, as I only used it for learning purposes, I could say goodbye to Perl, because I don't use it and I don't like it, or I could say goodbye to Visual Basic - for other reasons. All these languages are extremely powerful, they still do their job today, although some are too low-level for our everyday applications, some are just in a process of replacement (like Cobol, of course). However, I tend to keep these opinions for myself.
"Saying goodbye" in such cases is a strong statement, inherently sensationalist and biased, therefore my criticism toward the title. In a field like computer science, you can't and shouldn't use such titles. Such a title shows already how biased you (the author) are.
(My grandfather made very good money in the late 90s converting Cobol and assembly programs in banks and insurance companies to y2k.)
The summary of the article is programming is nuanced. You can attribute some nuances to OO design.
However, you probably arrived at this conclusion because you still see programming languages as having intrinsic paradigms and those paradigms meaning anything about computation.
The fact of the matter is that all programming languages are little more than notation systems for algorithms. The best programming language is the most accurate algorithmic notation system.
Functional programming is not that easy to do right in the real world. It takes some level of abstract thinking and understanding. Not too many people in this industry really want to do this level of thinking. They just want a recipe for getting things done with the least amount of thinking.
http://cstheory.stackexchange.com/questions/21705/what-is-th...
My point is basically made here:
This algebraic view of computation relates naturally to programming languages
used in practise, and much language development can be understood as the search
for, and investigation of novel program composition operators.I agree that the search for novel composition mechanisms is a prominent goal of PL theory and research but it is not the only one.
(Lambda calculus is an interesting foundation mostly for work on correctness of algorithms---indeed analysing cost of computations is harder. Chris Okasaki has done a good case study of analysing runtimes of purely functional algorithms / data structures.)
If you are happy to prove correctness of tree-sort, which does the same comparisons as quicksort, it's straight-forward.
If you want to talk about the clever in-place quicksort, you'll want to talk about a slightly more complicated model.
Either implicitly, where you still model everything as lambdas, or explicitly: you can model state (like an array) and its manipulation inside a lambda calculus framework just fine. Eg using linear typing, or something like Haskell's state monad (which has a pure description, but ghc is smart enough to optimize it to what you would expect when translating to machine code).
The sweet spot of lambda calculus is structurally recursive algorithms on trees. You can use it for other kinds of algorithms, but there it just makes things harder, and I've never seen it solve a problem that wasn't solved first by other means.
Actually, the best programming language is the one you're programming in right now. /s
Although my comment is in jest, I do think it has a lot of truth; I'm never going to write something for HPC in Haskell just because it has better algorithmic notation, I'd most likely choose Fortran or C. I'm not going to write a web back end in Haskell, I'm going to write it in Python, Ruby, Javascript, or any other language much better suited for the application. I'm not going to write statistical model analysis in Haskell, I'm going to write it in R, Python, Matlab, or Mathematica.
Saying that all programming languages are just little more than notation systems for algorithms doesn't actually reflect reality that programming languages are not just math operations, even if that's what they become in the compiler or to the CPU, and telling people that is disrespecting the entire industry and academic side of programming and computer science.
For what it's worth, Haskell has uses in finance for statistical modeling because you can model different currencies and equities/derivatives with ADTs and the type system will ensure you never confuse them in your code. Haskell syntax is beautiful and in many cases mimics mathematical syntax and as such is used frequently by mathematicians.
But I don't mean this to be a defense of Haskell. There are many posts that do it better, just yesterday there was a blog post on the front page about one startup's use of Haskell in its main product: http://baatz.io/posts/haskell-in-a-startup/
There are other languages like Lisp that directly imitate lambda calculus. Lisp is elegant because it is like programming in a syntax tree with the entire structure of the tree available for manipulation such that you can create extremely concise and expressive programs with less code.
For the example given for the Triangle Problem, the author isn't clear about exactly what behavior is being shared among the classes. The top of the tree, PoweredDevice, gives an indication, but my guess is that there are more responsibilities than just power, these responsibilities aren't being reflected in the domain model as they should be.
Instances of a class share behavior with other instances, it is the state that differs, i.e. the data being stored in the instance variables. In the example hierarchy, the state being stored is left out of the analysis, but it's the first place I would look for a missing domain concept. My guess would be that the most concrete class is going to be models of consumer peripherals, of which instances are intended to represent actual devices.
In this case a copier, which contains both a scanner and a printer, but not an actual discernible model of scanner or printer, would simply inherit from PoweredDevice. That it has this functionality does not mean it need actually have those in its class hierarchy. It is a job better suited for mixins, or injected dependencies.
Then an interface is a capability.
The upshot is that to teach OOP in Java, say, you need to talk about significant parts of the inheritance machinery just to get dynamic dispatch, perhaps later cautioning against implementation inheritance and even interface inheritance.
In Smalltalk, you can talk dynamic dispatch without messing around with inheritance at all.
For a statically typed language where dynamic dispatch is free from inheritance graphs (sometimes described by saying that subtyping is not inheritance) see Ocaml's structural subtyping via row polymorphic records (bit of a mouthful --- but I need to differentiate from Ocaml's module system which is structurally subtyped and supports inheritance!)
class A {
public int field1;
public void method1() {
System.out.println("A");
}
public void method2() {
System.out.println("A");
}
}
class B extends A {
public int field2;
public void method1() {
System.out.println("B");
}
}
is basically equivalent to interface C {
public void method1() {}
public void method2() {}
}
class A implements C {
public int field1;
public void method1() {
System.out.println("A");
}
public void method2() {
System.out.println("A");
}
}
class B implements C {
public A a;
public int field2;
public void method1() {
System.out.println("B");
}
public void method2() {
a.method();
}
}
As you can see both are basically the same except one little detail. The benefit is that you don't have to write the redundant "method2" method in class B. So the only time inheritance is ever useful is when you don't want to override all methods. Now someone tells you to model everything in terms of class hierarchies even when they don't need it right now and you've negated the tiny little benefit it ever had which means not having it in the programming language actually has positive consequences.Also in my code I define and use some classes.
I like the idea of classes. E.g., in my Web pages, I have a class for the user's state. When a new user connects, I allocate an instance of that class. Then I send that instance to my session state store server. To do that, I serialize the class to a byte array and then send the byte array via TCP/IP. The session state store server receives the byte array and deserializes it back to an instance of the class and stores it in an instance of a collection class. Works great. It's really convenient to have all the user's state in just one instance of one class. Terrific.
Encapsulation? I don't know what the OO principles say about encapsulation, but it looks useful to me as a source of scope of names and keeping separate any members in two different classes that are spelled the same. So, terrific: When I define a new class, I don't have to worry if the names of its members are also used elsewhere -- saved again by some scope of names rules.
Actually, I much prefer the scope of names rules in PL/I, but now something as good as PL/I is asking for too much!
But inheritance? Didn't think it made much sense and never tried to use it.
Polymorphism? Sure, just pass an entry variable much like I did in Fortran -- now we call that an interface. Okay. I do that occasionally, and it is good to have.
Otherwise I write procedural code, and the structure in my software is particular to the work of the software and not from OO.
I couldn't imagine doing anything else.
I've seen rule-based programming, logic programming, OO programming, frame-based programming, etc., but what continues to make sense to me is procedural programming with structure appropriate to the work being done. E.g., the structure in a piece of woodworking is different from that in metal working, residential construction, office construction, etc.
Wow! It's simpler than that: Can have BEGIN-END as a chunk of code with its own names. Any name used inside there but not declared there is inherited from the code that is active (in dynamic descendancy) and in the static descendancy.
E.g., can have ON CONDITION X; BEGIN; ... END; and execute that. That execution just says that, in the future, if see RAISE X, then execute the code in the BEGIN-END. Then that code can do GOTO Y where Y is a label known in the BEGIN-END block and in code in the dynamic descendancy. If the code of Y is 11 steps back in the stack from the RAISE, then all the 10 lower levels of code are exited. Lots of cleanup happens automatically.
So, the ON CONDITION is executing code back in the stack of dynamic descendancy -- darned cute. And can do such things quite generally.
So, code block A can have an internal subroutine B, call C, pass an entry variable to B, and the code of C can call B. B then can have values inherited from the code of A and use those values as B executes from the call of C. Did that once when scheduling the fleet for FedEx!
Once in some AI work at IBM's Watson lab, used a fancy case of that name scoping to save our project a few weeks of work and improved the quality of our product. Got an award for that!
That's a fast outline -- maybe not fully clear. Any questions?
EDIT: Reading a bit more into it, it's clear that's not it, thought the A/B/C example sounds like it could be done with a closure.
Uh, I've done a lot of programming in a lot of languages, but my main interest is not as a programmer but as a founder of a startup that needs some software!
And, what I really like is the name scoping.
For what else is significantly different in encapsulation seems a little obscure. Maybe the biggie difference is not only have the data with name scoping, but also have the code, the methods, stuffed inside the class and making changes in the data in the properties of the class. Okay -- so get to use the class and its methods without keeping track of subroutines/functions separately. Okay.
This is interesting to read. When OO was popular inheritance was at the top of the list as the OO killer feature. "your 'Manager' class is just an employee but with 3 extra methods, so you save so much copy and paste, etc etc".
I believed it at some point. Then perhaps 15 years later, after everyone has been bit by deep, confusing inheritance they had to maintain and debug, inhertance is the devil.
It is just interesting to observe how once a selling point of OO is now a big giant warning to stay away from.
"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."
After he quotes what Alan Kay says...
The notion of object oriented programming is completely misunderstood. It's not about objects and classes, it's all about messages.
Armstrong then says:
Erlang has got all these things. It's got isolation, it's got polymorphism and it's got pure messaging. From that point of view, we might say it's the only object oriented language and perhaps I was a bit premature in saying that object oriented languages are about. You can try it and see it for yourself.
If you'd like to read some further other anti-OO ("as practiced") quotes, see my fortune clone @ https://github.com/globalcitizen/taoup
OO solves a set of problems albeit with tradeoffs
Functional solves a set of problems albeit with tradeoffs
There. We can all go back to our tea.
Khronos are the ones that still leave in pure C world.
Hence why most researchers went to CUDA in terms of language support and now they are trying to catch up with SYSCL and SPIR.
If something doesn't need to be an instance, it probably shouldn't be one.
This article articulates a lot of problems I've noticed in OO code, I think it would be foolish to ignore it. My life as a developer became 10 times easier once I realised some of these same pain points and pivoted, or maybe even more so.
In school I was taught all about OO coding practice and I think he's right, they were wrong.
Why is it then that programs written in OO outnumber FP software by 100000:1 or more. For example most of the software written for iOS and macOS are written in C++, Objective C or Swift. All three are class-based object-oriented languages.
What you say may not be true or may not be very important.
That argument assumes the average developer, average university, average company can look around and is able to effectively pick, understand (this implies ability to learn) and evaluate merits of a framework or language. Most don't even have a choice. They are taught, or maint the code base or listen to what is told by management and that's their choice. Managers hire to the code base that's already there and to languages / frameworks they know. Developers who just want a job (nothing wrong with that) will pick and learn languages that managers hire for.
You are forgetting about all the spreadsheets.
Looking back now on 25 years in software development, plain old imperative programming still bought me the most in terms of getting stuff done (Banana problem). With a decent set of standardisation and sane language defaults a mostly imperative approach will get you very far.
Golang hits that sweet spot very decently for me. Missing type generalisations are an impediment from times to times though.
Here's a concrete example of object oriented design:
To understand the problem domain, go to https://whiteboardfox.com and click Start Drawing > Create Whiteboard, then draw something. Play around with different colours, erase some lines, try undo and redo, etc.
Now here is my class diagram for implementing it: https://s1.whiteboardfox.com/s/7762255cabe34643.png
I honestly don't see how you could implement it without object oriented design. Surely it makes sense to have a Diagram class that encapsulates a list of strokes and pictures? Isn't it easier if the Diagram class exposes addStroke() and removeStroke() but does not reveal how it's implemented? And shouldn't I have a separate view class which encapsulates how much zoom and pan the user has applied to the diagram?
Could you implement Undo and Redo actions so neatly without a command pattern?
And isn't it lovely that the ViewController can switch between different modes (Pencil Mode, Eraser Mode, etc) without needing to know anything except a small interface that is common to all modes?
I actually get a little thrill when I think about how cleanly this design addresses the requirements. Could I get that feeling if this were implemented in a functional programming style?
Data structures and operations on them are indeed a useful concept. Not limited to oop, though.
> Could you implement Undo and Redo actions so neatly without a command pattern?
Persistent data structures make it even easier. You just keep a list of references to the old states around.
> I actually get a little thrill when I think about how cleanly this design addresses the requirements. Could I get that feeling if this were implemented in a functional programming style?
I can't predict your feelings, but what you described could very well be done in eg Haskell. (You just wouldn't use oop classes, but you can abstract over similar concepts.)
Have a look at http://shaffner.us/cs/papers/tarpit.pdf for some thoughts on software design. (The paper advocates using relations. I have seen them work very, very well in functional settings. Just the opposite of ORMs.)
"Software Engineer, Philanthropist, Astronaut, Shark Hunter, Breaker of Chains, Lord Commander of the Snack Bin, Protector of the Repo, and Part-time Cat Dad"
Too much or just right?
And as for 'Protector of the Repo'... basically what you're saying is that you're a repo man?
that seems to be really hot right now
Snicker. I totally love this and am stealing it from you.
[1] in the sense 'workmanship'
A[nother] minority view getting mainstream (for us) exposure because a few other dinosaurs and hipsters agree.
People need to chill out about this stuff. You don't like OOP? That's great but if it's alright with you, the rest of us are going to carry on with it while you fade into irrelevance.
Of course not everything fits an OOP flow but even Java (the very most verbose OOP IMO) will let you do things linearly or functionally if you want to.
But, yeah. I agree with you.
But your comment is purely ad hominem -- to the point of calling the author a dipshit with absolutely no basis -- and does not engage TFA (breaking the first two comment guidelines at https://news.ycombinator.com/newsguidelines.html).
However, I don't think the style of this article is Medium's fault. I find it more and more common that I see a headline and have to skim through the whole article to find out what the point is. I don't know if it is from watching too many TED talks or just poor writing (not mutually exclusive, of course). But then I go read the comments and see tons of people commenting about how they loved the article and how well written it was so maybe it is me that is wrong.
On second though, no. It's the children who are wrong.
What if every Java developer who discovered the immense cerebral gratification in Scala decided to write an article with the theme "Aww shucks... Frick You Java, I wasted so much time on you damn it !!! I am going to Scala and I am never coming back."
Also, the examples the author gives maybe weak. Inheritance breaks my code ? If it's code I don't own I use dependency management. If it's code within the same team then code review before commit ?
The reference owning example for encapsulation assumes references are globally held ?
(PS: Just using Java/Scala here but feel free to vote me down if the experience is different with other language pairs. Oh also that I am having dirty dreams of leaving Java and indulge in Scala's monads as I recently discovered I wasted time on Java)
https://channel9.msdn.com/Events/GoingNative/2013/Inheritanc...
Judging by the UNIX paradigm of the command line tools, the idea is clearly out there.
Instead of objects, do modules - things that do one thing, and carry minimal dependencies.
You need a banana? Grab the banana module. You need a banana with ice-cream center? Feed the "center" callback of the banana module with "ice-cream" instead of "banana intestines".
You need a copier? Grab both printer and scanner.
Is there any existing language that i'm describing now?
Objects are basically ADTs with support for polymorphism and inheritance.
It's curious to see that OOP hate coming from someone that got a chance to work in Smalltalk.
Of course I may be dumb and what I'm asking for is ColdFusion or Business Objects or Salesforce or something.
It's easy to complain about them but in most cases I see it's a misuse issue.
The reason is that a computer is fundamentally all about state and you need something to manage the that state. This is the antithesis of FP. OOP manages state somewhat nicer.
Explicitly handled state + mutations, polymorphism, implicit safe concurrency, fast local calls + opportunities to optimize + safe and easy to reason about.
And then, once we have hard data, we should have the courage to follow the data, even if it means throwing away our cherished pet paradigms and methodologies.
That has been my cumulative verdict so far learning FP - perhaps this view would sway one way or other as I learn more about it
Sure, the Copier has both Printer and Scanner, however, in practice, the "Start" function on a Copier differ from either -- it starts the scanner and forwards it to the printer. It might also print multiple copies.
Point being, the `start` functionality here differ from both Printer and Scanner hence, the `start` method shouldn't be inherited.
The components are so well defined as objects since they have the luxury of being tangible. But using them in a pure manner with zero local state makes them so easy to reason about and reuse.
More can be said about Redux but I'll leave it there.
Say I have a form with 4 fields and a "Submit" button. The submit button's `onClick` tells its parent that it was clicked. The parent calls `getValue()` on all of its children (the form fields) and dispatches a `updateMyDatabase()` action.
My example may be controversial to some as my form fields have state. Some may say that on every keystroke, the form should have an action called that updates the `centralState.formValue`. But regardless of that, I think it's still evident how there's OOP involved?
Usage of interfaces, immutable final keyword, and anonymous methods are powerful and flexible enough to move beyond the constraints of pure OOP.
FYI, in OOP paradiam all inherent, encapsulate and so concepts are for one goal – design a better interface, that also follow how the real world be designed, for example, power outlet at you home.
A good programmer writes good programs.
The tools don't really come into it.
https://en.wikipedia.org/wiki/Taligent
https://en.wikipedia.org/wiki/AI_winter
These are two C++ and one Lisp failure. And language did play a big role in each failure.
Class Copier { Scanner scanner; Printer printer; function start() { printer.start(); } }
---
Placing a Start() in a PoweredDevice base class doesn't make sense in the real world. There are plenty of "powered devices" that don't have start buttons. A phone, a fish tank pump, a smoke alarm, none have a "start." A powered device should have just that, a PowerOn() and PowerOff() or SetPower(bool isOn). I wouldn't even create a PoweredDevice base class unless you have a reason. This is the main fault in your design.
Scanner.Start() should return a byte[] which is the result of the scan: byte[] Scanner.Start(); A scanner is an input device.
Printer.Start() should take an argument of byte[] as to what it is to print: void Printer.Start(byte[] byteArr); A printer is an output device.
Having said that, your Copier class would look like this:
Class PoweredDevice
{
void SetPower(bool isOn)
{
...
}
// Start() doesn't belong here.
}
Class Copier : PoweredDevice
{
Scanner scanner;
Printer printer;
void override SetPower(bool isOn) {
printer.SetPower(isOn);
scanner.SetPower(isOn);
base.SetPower(isOn);
}
void Start()
{
byte[] document = scanner.Start();
printer.Start(document);
}
}
This can easily be enhanced to handle copy counts: void Start()
{
byte[] document = scanner.Start();
for (int x = 0; x < copyCount; x++)
printer.Start(document);
}
Ideally you wouldn't even make an inheritable Start() method. The Scanner class would have a byte[] Scan() method and the Printer class would have a Print(byte[] byteArr) method. You're trying to ram a square peg into a round hole. Use inheritance when it is convenient and makes sense to do so. Don't force it. Think, what does a scanner and a printer have in common that works the same, then put that in your base class. A power button is about it.A lot of inheritance is done backwards. You make your classes then find commonalities and put that in your base class. Only create a base class first if you've thought about your object model and you know the commonalities.
Also, there is no reason to make your inheritance chain deep, just because. Build your objects in a way that makes sense. Don't write code or base objects you will never use. You can always insert a class in the chain when necessary.
Mastering OOP is hard, and people who have mastered it get paid a lot of money for their skill. It took me a few years to really understand how to design with it. It's invaluable though. A good object model is a thing of beauty, and a hell of a lot of fun to design.
Edit: I don't know why the editor won't keep the CR's.
I'm at a loss as to what this has to do with encapsulation, and even less able to understand how any language with user-defined data types is going to be able to avoid it.
Actually, immutable containers isn't good enough; you need all the leaves to be immutable too.
It is not perfect because the objects your class may encapsulate may have been passed in a constructor by reference, meaning there is code outside your class that can mutate those objects.
Even in functional programming languages encapsulation is a useful concept - often in a functional language it is implemented with opaque data structures that can only be manipulated by the module which creates them (constructors are not exported).
I don't think it always means there are not shared references; it all depends on the role of the data in question.