Goodbye, shitty Car extends Vehicle object-orientation tutorial (2011)
mail-archive.com
mail-archive.com
The author is obviously an experienced programmer, so I suspect they've forgotten what the mindset of an absolute novice is like, and to start programming with zero prior exposure to the concepts.
For the novice being introduced OOP, the `Duck / Cat / Dog <: Animal` analogy is useful to demonstrate how things like inheriting methods and sharing behavior works. It helps one start to think in terms of hierarchies and the boundaries between "concepts" in code. Obviously, in 'real' code, you won't be writing much that involves a literal hierarchy of animal species.
That's not the point. A beginner knows what a dog and a duck and a cat are in the real world. The "a-ha!" moment is mapping real-world relationships into a brand new abstract environment called Object Oriented Programming.
When you've never written a line of code in your life, abstract things like "network protocols" and "file I/O" and "ImageDisplay" (which is neither an image nor a display) are confusing, and so frustrating, and so not useful for teaching a complete novice.
Don’t worry about Wimp Lo, he’s an idiot. We’ve purposefully taught him wrong… as a joke.
Composition is an infinitely better method to teach if you have to teach OOP. That way you don’t need to unlearn what you thought was the right way but is actually useless at modeling almost anything.
Edit: A previous version of this comment was more wordy in stating this and mentioned that there are formal proofs to this effect.
Or go try and solve a moderately complicated anything without allowing yourself abstractions with error. I won't wait on you, because your solutions will never terminate in my lifetime for even things as simple as chess which is a hell of a lot simpler than reality.
Obviously this only gets worse when we impose reality - we don't actually have infinite space on our computers. They don't compute for an infinite amount of time either. But notice that before we searched for infinite space and time and we failed? When we move down to finite space and finite time we still have the property of completing after full enumeration. We have a finite amount of computing capacity. We have a finite amount of computational storage. Yet the growth rate for unabstracted game trees is exponential. Let c be our constraints.
An abstracted game maps n states to one state. So it has log_n(X) where X is your state count. An abstracted game has X states. Since log_n(x) < X we know there exist terminating algorithms for abstraction that are not terminating for unabstracted because log_n(x) < X when X = c. So we get log_n(X) < c when X = c.
This is actually much less than the real world gains. Since in learning we get the policy expectations multiple times over the game graph and the convergence guarantees relate to the complexity of the graph you get a much more worthwhile window than just the difference of log_n(X) versus X. For much tighter bounds check out game theory research. They get error bounds on the abstraction error too by choosing clustering solutions with provable properties. So it really has a much stronger formal treatment than you might imagine.
One abstraction that is very useful is linear algebra which is an abstraction without errors. Same goes for category theory. Grothendiecks work wasn't about tolerating errors in abstractions either. Simple abstractions like generic containers are also not about ignoring errors.
Honestly, the random segues to diagonalization arguments, game theory, continuous functions, RL and Bellman equations(WTF!) sound like stream of conciousness random ramblings and an attempt at "out jargoning", much less a "formal proof". Reminds me of this story by Tadelis.
https://thecorrespondent.com/100/the-new-dot-com-bubble-is-h...
"We use Lagrange multipliers," one of them said. And for a second, Tadelis was astounded. What? Lagrange multipliers? But Lagrange multipliers don’t have anything to do with ..."Then it hit me," Tadelis recalled. "This guy is trying to out-jargon me!"
Bullshit. Your claim that there is no abstraction error in linear algebra (when run on computers - we're in subdiscussion related to programming) is false.
Computers can't represent all numbers [1]. They can only represent the computable numbers in theory and even then only a subset of computable numbers can actually be computed. Therefore, there is abstraction error in linear algebra on computers. This isn't at all a theoretical thing. This regularly has implication on algorithm design. You ought to have known this, because the truth about floating point numbers has been well publicized [2].
> Same goes for category theory. Grothendiecks work wasn't about tolerating errors in abstractions either. Simple abstractions like generic containers are also not about ignoring errors.
You're just ignoring that these things when implemented in computers actually do have error, because you find it convenient. You're also apparently not self-aware enough to realize that this agrees with my central premise. Think about your thinking and you'll notice that this failure is indicative of your own minds belief in wrong abstractions being right - otherwise you wouldn't have been able to make this error.
> Honestly, the random segues to diagonalization arguments, game theory, continuous functions, RL and Bellman equations(WTF!) sound like stream of conciousness random ramblings and an attempt at "out jargoning", much less a "formal proof". Reminds me of this story by Tadelis.
We're talking about learning as it relates to abstraction. If you can't see why a learning problem formulation is relevant to discussion about whether learning an abstraction is appropriate that says a lot more about your reasoning than it does my articulation.
As a reminder the OP said:
> Teaching people to think about abstractions wrong is not in any way helpful. Everyone I know including myself had to spend years writing bad code before realizing that this way of thinking is counterproductive.
I've disagreed with the claim that teaching a bad abstraction that has to be unlearned is bad. I've shown that in learning problems, teaching a bad abstraction can actually be good. I've given proofs to the effect that there are times in which teaching a bad abstraction produces algorithms which terminate in situations where teaching a perfect abstraction doesn't terminate. I've given you links to mathematics showing that given a bad abstraction as a starting point you can produce agents which outcompete something which doesn't use an abstraction. The paper in question takes a bad abstraction and improves upon it after getting it during the challenge of a more specific problem.
> "We use Lagrange multipliers," one of them said.
Hacker News guidelines call for more thoughtful points, not less thoughtful points, as threads get deeper. It also calls for assumption of good faith. It calls for increasing nuance [3]. What you are doing here - it isn't that. At the risk of starting something painfully obvious, talking about random shit people that aren't me said that you found dumb isn't actually relevant to the discussion. This is a subthread that is on the topic of learning and abstraction. I'm talking about both topics. You aren't.
> random ramblings
I'm sharing something deeply counterintuitive but true because I think people might find it interesting. Right algorithm + correct data can be worse than right algorithm + wrong data. This is deeply counterintuitive and I find it very fascinating, but it falls out of the formal definitions of utility under multiple different learning frameworks. Something isn't bad because it has abstractions with error in it. Teaching an abstraction with error in it isn't bad either. It isn't even bad when you can find the error - because you're in a more specific situation the learning problem changes, in the general case it was too hard to compute the unabstracted best thing to do, but you can reduce the error in your abstraction when you get more specific because there are less states and so the learning problem becomes more tractable.
[1]: https://www.cs.virginia.edu/~robins/Turing_Paper_1936.pdf
[2]: https://floating-point-gui.de/
A person might try to respond to this point by claiming arbitrary precision numbers exist. That person is lying to themselves. They don't exist. The abstraction error is just in a different place. Go back to the definition of Turing Machines and observe again that not all numbers are computable - we are in an abstraction subject to error and you can't escape this while staying in the Turing framework [1].
The fact that you had to change my statement to add computers to it already makes it clear that you know my statement is correct. Don't put words into my mouth to prove statements that I have never made false. You also failed to realize that the tweaked statement you put in to my mouth is still correct!
You fail to realise that we can prove theorems about linear algebra using computers without actually using floating point numbers. Linear algebra can be infinite dimensional and not just be defined on the complex field, and this doesn't introduce any errors in to the abstraction or theorems being proved, with or without computers. Your assumption that linear algebra on computers is exclusively about floating point number crunching tells me that you don't understand what abstraction linear algebra represents. Linear algebra is the study of linear maps. This is a good book for you to get started [1]
You also failed to address the gazillions of abstractions that don't have errors, some of which I have listed along with linear algebra. For those familiar with proofs, Just one counter example can prove your sweeping statement "all abstractions have errors" false. We are discussing math, not physics. And if you want to restrict yourself to computable functions- modulo arithmetic with groups, rings and fields suffices as a counter example.
It isn't even clear what you mean by abstraction at all.
> We're talking about learning as it relates to abstraction.
The OP was not discussing learning at all. Your segue into machine learning concepts is completely unrelated to the topic being discussed. So is game theory. I am familiar with virtually all the topics you are discussing, so you can skip the citations. I find no coherence to any of your segues.
> You're just ignoring that these things when implemented in computers actually do have error, because you find it convenient.
You are completely out of touch [2][3]
[1] https://linear.axler.net/LinearAbridged.pdf
[2] https://en.wikipedia.org/wiki/Univalent_foundations
[3] Mathematician Kevin Buzzard https://www.microsoft.com/en-us/research/video/the-future-of...
Obviously I realize we can prove theorems. If I didn't realize it was possible to prove things I wouldn't claim things were provable. This is false by contradiction with my previous statements. Your worldview of me isn't consistent.
> You also failed to address the gazillions of abstractions that don't have errors, some of which I have listed along with linear algebra.
Why should I have to prove that abstractions without error don't exist? They do. I never claimed they didn't. My point was that abstractions with error reduce computational complexity. This gives them room to outcompete perfect abstraction for sufficiently complex problems. It honestly seems insane to me to not believe what I'm saying is true, because it is true. I can't fathom how the concept would be impossible to grasp. Showing that we regularly use error filled abstractions is more important than demonstrating something I don't intend to show.
> The OP was not discussing learning at all.
I literally quoted him saying there was never a reason to teach bad abstractions. Teaching is related to learning. Abstractions are related to abstractions. So abstraction and learning were a topic of discussion. Even the original post we're under is about teaching programming technique. Which is about learning, because of the relationship between teaching and learning. It is also about abstraction, because problem modeling is very related to abstraction.
> "all abstractions have errors"
Ctrl + F shows no instance of this except for you saying it. What do you think I'm claiming? I'm really confused? You seem to think I'm saying something I'm definitely not saying.
Notice how when I said it I put parantheses and explained my reasoning as to why I felt your claim was your claim? You meanwhile misquote me. Strict quotes imply actual attribution, but I never said what you claimed I said.
I don't want to talk to you anymore. I very much don't appreciate your comparison to someone who just makes stuff up. I found that very rude and insulting. A kind person would help correct me if my reasoning was wrong and I would appreciate it, but you haven't done that. When you quoted me, it wasn't even something I said or tried to argue. If you were trying to make someone have a worse day, congratulations, you did.
>>> Bullshit. Your claim that there is no abstraction error in linear algebra (when run on computers - we're in subdiscussion related to programming) is false
>> You fail to realise that we can prove theorems about linear algebra USING COMPUTERS
> Obviously I realize we can prove theorems. If I didn't realize it was possible to prove things
You have been arguing in bad faith by either putting words in my mouth (when run on computers) or deleting key phrases from my reply (USING COMPUTERS - reinserted by me)
These 2 statements by you contradict each other
"Bullshit, Your claim that there is no abstraction error in linear algebra (when run on computers - we're in subdiscussion related to programming) is false "
" Obviously I realize we can prove theorems" -----> USING COMPUTERS
It is clear that you thought of computers as IEEE floating point number crunching machines, with implicit floating point errors. All of your arguments rested on this irrelevant point. You even condescended to teach me about floating points using citations not needed by anyone who has attended CS101.
You failed to realize that finite sized representations of computable real numbers exist, by definition[1]. One such representation could be a finite sum on surd basis, instead of using a binary basis eg sqrt(2) instead of 1.414.... and the most general form is a theorem prover like Lean.
All of these misunderstandings in your head because you failed to realize the gist of the Church Turing thesis ' - Everything that you do with your brain and paper can be duplicated on a computer.
In any case, I am glad you learned something today. Computers don't relate to math via IEEE floating pointing numbers, the connection is a lot deeper[1]. You won't acknowledge this, but frankly speaking, I can't really stand out- jargoning pretending to be an honest discussion. I did give an opportunity to you to correct yourself with my first comment. But you only doubled down - more jargon, bad paraphrasing of diagonalization, putting words in my mouth, followed by removing key phrases from my replies amongst other bad faith arguments.
[1]
https://en.wikipedia.org/wiki/Church%E2%80%93Turing_thesis
To establish that a function is computable by Turing machine, it is usually considered sufficient to give an informal English description of how the function can be effectively computed, and then conclude "by the Church–Turing thesis" that the function is Turing computable
I despise that you lied about whether we were talking about learning and abstraction. I don't like talking with people who blatantly lie. I consider lying bad.
I also dislike that you misquoted me. You put quotes around words I didn't say. I didn't do that to you. You say that I did. You lie when you say that. I gave you my interpretation of what I felt you were claiming. I even explained why I felt you claimed that. This wasn't under the quote symbol. Yours was inside quotes. Yours was a lie. Mine had your original quote, unaltered, with my interpretation below it. I was in error. I admit that. I was trying to get at the heart of my point - that erroneous abstractions aren't inherently bad. Outcome error is much more important than input error.
I don't agree that you've taught me anything - you just try to call me incoherent because what I'm saying is true but you employ motivated reasoning to avoid having to refute it. If you actually understood what I'm saying - which obviously you don't, which is a big part of the problem here, you would agree with me. Or at least, I think you would.
That simple problems are easier to solve and sometimes an actual solution is better than no solution really isn't that complicated a thing. Or controversial. I'm sure plenty of people understand it.
There are so many times in life where my point holds. The use of floating point is one. Perhaps you didn't notice that ML engineers frequently choose to move from float64 to float32 to float16 to float8? Perhaps you didn't notice that services all throughout the computing industry choose to meet an SLA, minimizing latency sometimes at the cost of optimal solutions whose computation isn't realistic given their computing budget. I don't know. But you're definitely not actually teaching me anything. Your just not understanding me. So this conversation is pointless.
I'm still just as convinced of the truth of the idea that it can be very wise to accept a bad abstraction, one that has error, rather than a perfect abstraction. I can't even fathom how to go about the opposite. How would a child go from knowing nothing to knowing everything perfectly without moving through areas of bad abstraction along the way?
I feel you are mean. I'd rather we stop talking about this together if we're not going to actually engage with each other on the topic under discussion.
1. Do you disagree with my claim that the runtime of learning algorithms depends depends on the graph size in both game theory and reinforcement learning problem formulations?
2. Do you disagree with my claim that abstraction reduces the number of states in the graph?
3. Do you disagree with my claim that since abstraction reduces the number of states in the graph the learning algorithms which run against them can complete more quickly because there are less states?
4. Do you disagree with my claim that algorithms which can compute a solution can have a better solution than algorithms which don't compute the solution?
5. Or if you don't disagree, can you admit that we agree on these things? Because when you just act like I'm not making any points it makes me feel like you are trolling me and being a jerk, not actually trying to talk to me.
I'm still just as convinced of the truth of the idea that it can be very wise to accept a bad abstraction, one that has error, rather than a perfect abstraction. I can't even fathom how to go about the opposite. How would a child go from knowing nothing to knowing everything perfectly without moving through areas of bad abstraction along the way?
Which numbered point do you feel is incoherent?
- When we tried to solve chess we couldn't, the branching factor was too much.
- Go, it was horrendous there too.
- Poker, terrible there too.
But you want to dismiss me on the basis of jargon right? So here you go. Bellman coined the term curse of dimensionality. Combinatorial explosions happen because of branching factors in game graphs. Computational complexity for algorithms are defined with respect to this graph in both time and space for many learning algorithms. Because the games get so big the curse of dimensionality forces problem relaxation. I used ~words~. I must be an idiot. Feel free to dismiss me, I guess. I heard you heard someone else use words once and they were ~wrong~.
Hey wait a second. You're using words too. Does that mean everything you say is wrong?
Can you stop pretending I'm talking about things that are computable when talking about things that don't terminate? When someone says that computation is only defined for the computable numbers responding with the claim that they don't understand "that finite sized representations of computable real numbers exist" is honestly either stupid or malicious.
Yes they were. They were discussing teaching, which is related to learning.
Moreover, the person they were responding to - they also were talking about learning. They talked about how it was good to learn an abstraction that wasn't perfect.
Even the article is about whether we should teach one abstraction or another.
> You are completely out of touch
Gaslighting is abusive. Stop abusing me.
> I am familiar with virtually all the topics you are discussing, so you can skip the citations. I find no coherence to any of your segues.
This is an argument from authority. It is a fallacious argument. If my segue is incorrect, you need to show it from the structure of the argument, not by appealing to your authority, which is irrelevant; you might be great - I'm not saying your not.
You are great; I'm not saying you're not. I'm sure you're intelligent and smart and witty and cool. But who we are - it doesn't matter. We're irrelevant. The ideas are all that matters.
If I'm wrong - why isn't chess solved? Why do we approximate solutions? Meanwhile, why is checkers solved? Why do games with simpler graphs get solved perfectly but more complex games with more complex graphs not get solved perfectly? Please back up the ideas you would be advancing were it the case you actually disagreed with what I claim is provable. If you actually understood my point, than you should know that the opposite of my point isn't that perfect abstractions exist - it is that the run time of specific learning algorithms aren't correlated with their input size. Your trying to get me to defend conclusions I didn't make, treating me like I'm stupid and comparing me to people who are rambling. You are being abusive. Lying. Gaslighting. Attacking me as a person rather than my ideas. Appealing to authority.
I'm sorry that I thought you were referring to linear algebra when used as abstraction in the way I meant it - I'm talking about computational abstractions; stuff with many dimensions, compressed to be in fewer dimensions. That happens when we do linear algebra on computers in practice. I thought it was reasonable to point this out, because my claim is closer to "abstractions with errors in them can be useful" than it is to "abstractions without error don't exist". But I feel like you do understand me in this - because you seem desperate not to admit this. To pretend we can represent all things in finite space, when we can't, because the infinite things can't all be represented in finite space. And it seems to me the only reason you would be so desperate not to admit this - to try and throw up so much confusion about this idea - is that you do understand me. And you understand that if you surrender on that point, you admit I'm right. So I think you already know I'm right. And you're just being mean intentionally. I think I probably offended you. It best explains the inconsistency in your reasoning. So I'm sorry about that - and I assume it was sometime in the past, because my first replies didn't deserve your malice.
I'm using abstraction in the sense of 'blueprint abstraction' from game theory. This is basically compression of the input to your learning algorithm. There exists lossless compression - perfect one to one abstraction. There is also lossy compression - given one compressed form it could be any number of uncompressed forms. Abstraction with error is then compression with error. What I was trying to prove and what I still believe to have proved is that some algorithms when given an input of unbounded size have the property of not terminating. What I then tried to show was that abstraction breaks the proof of non-termination, because it breaks the core assumption of diagnolization - that there is a one to one mapping. So the proof of non-termination doesn't hold.
> I am familiar with virtually all the topics you are discussing, so you can skip the citations.
I literally linked to a paper that used abstractions in the way I meant it. So maybe you should not skip citations? Clearly you don't know the fields as well as you think you do.
> I find no coherence to any of your segues.
It isn't a segue; it is what you asked for. I gave you a proof that unabstracted learning problems can terminate where abstracted problems terminate.
> You are completely out of touch [2][3]
This is such an ironic statement; here we were discussing what abstraction techniques we should teach when teaching computer programming and you're trying to complain that I'm the one who is out of touch when I say computers use abstractions. They definitely do. In fact, they use abstractions with error.
I really can't, because other people might believe you if I don't; I don't hate them. I want them to know the truth. So I'll oppose you strongly, for their sake, so they can discriminate between my counterintuitive truth and your rejection of the truth on that basis of confusion.
Go read page 173 of Artificial Intelligence: A Modern Approach. I'll quote Norvig here. "Because calculating optimal decisions in complex games is intractable, all algorithms must make some assumptions and approximations." Now go to page 172. I'll quote Norvig again. "One way to deal with this huge number is with abstraction: i.e. by treating similar hands as identical. For example, it is very important which aces and kings are in a hand, but whether hand has a 4 or a 5 is not as important, and can be abstracted away."
But, the discerning might ask, what of the talk of the infinite? Why does Josh speak of such an absurd thing? Isn't it irrelevant? It is not. Go to page 611, "Non-Cooperative Game Theory". I'll quote him again for you, "With this observation in mind, the minimax trees can be thought of as having infinitely many mixed strategies the first player can choose." The thing to notice in this quote is that we have a simple game - very simple. Yet Norvig just explained that in this simple game we have the quality of a tree of infinite size. This growth to infinite is actually very normal - mixed strategies are continuous and we have proofs that mixed strategies are the solution for a variety of different games involving imperfect information.
You're claiming that if someone points out that abstractions with error - which are literally impossible to avoid - are useful despite the error, then they're just pretending to know things. But anyone complaining about abstractions having error as a basis for abstraction being wrong is fundamentally missing the point of abstraction.
We need abstraction. It isn't illogical to tolerate the error. It is suicide to not tolerate the error, because you won't terminate - which means you can't react. Haven't you ever wondered why people aren't purely rational? Why we think fast, not just slow, but also fast? These questions have answers. You can look at the foundations of learning in terms of graphs and see why it has to be so. I'm sorry it goes over your head, but it is fascinating regardless of whether or not others understand it. And I think it is worth sharing, because it is fundamental truth.
This is formally provable, but to put that another way - I don't care if you want to be wrong; good for you, saving yourself some time. Enjoy your day.
The idea that some computer programs take too long to finish if their input size is really large isn't a complicated one. You try to discredit the existence of this basic truth by complaining that I'm using jargon, but my jargon is really just vocabulary. I bring up the bellman equations and counterfactual regret, because algorithms built with respect to these things operate against the graph of the game. By invoking them, I'm not being incoherent. I'm firmly rooting my claims in the computational complexity of the algorithms.
I'm not doing this because I'm confused. I'm doing it because game graphs have very particular properties. When you add additional moves to a graph on each turn the growth rate isn't one move more of complexity - the number goes in the exponent. So modest amounts of additional states lead to an combinatorial explosion of complexity. They make the graphs extremely big. In practice, as well as in theory, this makes it so the algorithms don't terminate before the universe is expected to.
I'll give a practical example of this so you don't rant about lagrane - but also so that everyone reading this will realize you are full of shit and only pretending to know what you claim to know.
A practical example of this is that we've managed to solve checkers, but chess is too complicated. The number of states in the graph is so high that we can't enumerate all of them in a reasonable amount of time. Go is a more extreme example than chess. Anyone who doesn't know the jargon can easily look up terms like "branching factor" and "solved checkers" and "solving chess" and "solving go". They'll quickly find that you were misleading others with regard to computational complexity not being relevant to whether error in learning is reasonable.
We need to do something to make the problems easier in order to make progress. So we do. Sometimes, when we are lucky, we can use perfect abstractions that make things simpler. One thing you seem to think I'm saying, but which I'm definitely not, is that perfect abstractions don't exist. That is you just being confused about what I'm saying. It isn't something I've claimed. Instead, what I'm claiming is that sometimes perfect abstractions aren't enough. Again, chess can be used to make this point. You can get some perfect abstraction by doing things like rotations on the end game tables. Yet this doesn't actually save you from the game tree being enormous. Despite having perfect abstractions, we still use approximation when we try to learn the best thing to do in a chess position.
I think you don't give me enough credit. Your entire interaction with me has tried to imply that I'm just pretending to understand something in order to win an argument. You seem to think these things I'm saying are about big words, because I'm incoherent. That isn't true.
If you look up the comment chain you'll find, paraphrased, that one person said something to the effect that learning something that is wrong can be productive for someone who is learning even though it has error in it. Then another person disagreed with that claim on the basis of error existing in the abstraction and later being corrected.
So clearly, we definitely were discussing (1) abstraction and (2) learning. Fundamentally, it isn't jargon if I choose to try to make my point by talking about (1) learning theory and (2) abstraction and how it relates to learning theory. You try to act like I'm being incoherent, but really I'm just thinking from first principles. We're discussing learning and instead of thinking about it in terms of programming, a thing where there is plenty of debate about what is the right approach, I'm thinking about it from a lower level.
That means my claims are actually a lot more limited than others. More nuanced. This is in keeping with Hacker News guidelines. Our replies are supposed to be more nuanced and thoughtful as we get deeper into the comment tree. When we disagree with each other, we're supposed to be teaching each other something.
I don't think it is wrong of me to think from first principles, nor for me to share my thinking from first principles. I can't offer what is the best thing to teach, but I can suggest with confidence that it isn't the case that an abstraction having error and later being corrected while solving more specific problems isn't enough to prove that teaching that abstraction is bad. It might be suggestive, but it isn't sufficient.
It really is the case that there exists problems which in their full unabstracted state the problem is too large to solve even if you use a perfect abstraction for certain learning algorithms. That is why we even do approximation. That you can apply approximation to the space of the inputs and reduce the size of the problem means you can connect the introduction of error via abstraction to the simplification of problem complexity.
I'm not saying this to sound smart. I'm saying this because you can actually do that. It isn't a universal result - there are some learning frameworks that aren't defined with respect to a graph. That is why I'm so careful to talk about learning algorithms that do the definition in that way. Your entire railing against me for arrogant "jargon" is actually an attack on me having been cautious to not make claims that were too bold.
Frankly, I think you should consider the opposite of my point to see if you really believe it. If you believe I'm wrong than it means you believe that it is possible to learn without ever learning errors. So for example, you believe that all babies ought to be able to instantly know all things - even things our society doesn't know anything about as of yet. I'm not saying this to put you on this position or imply that you believe it. I'm saying it to make it more obvious to you that what I'm saying isn't actually a controversial thing. The fact that believing the opposite would create absurd beliefs is suggestive of the fact that I'm right about there being real benefit to being willing to learn and teach abstractions with error.
Perhaps most importantly - I linked a paper in which an abstraction with error was better than the best results we've gotten without abstraction with error in a game theory research paper. So I have an existence proof of my claims. I'm right and you're just too conceited with regard to your presumption of my idiocy to see it. If you were less interested in being mean and more interested in actually talking to people, the conversation could have been a lot more interesting.
In my estimation of our conversation you're struggling to avoid contending with my points. You attack me, because you can't contest with my ideas. You try to claim I'm rambling, because you can't find flaw in my reasoning. You had a preconception that I was wrong and you never bothered to really engage with what I was saying, just assuming I was wrong. And so, you are; you've become a creature of rhetoric, attacking others character rather than dealing with ideas as a person ought to.
Look at a classically inheritance-based system: a GUI library. You'll see lots of inheritance. Buttons and Checkboxes extend ClickableThing, ClickableThing extends Control. Whatever.
And then you compose those controls on a Form.
Look at a classically composition-based system: a video game entity-component system. You'll see Monsters composed of GraphicsObjects and Animations and WeaponSlots and AIAgents.
And you implement all of those components in an inheritance hierarchy, or else your GameObjectThinger can't have a list of them that can grow or shrink at runtime.
So keep saying "teach composition, not inheritance". You might as well tell musicians to study "rhythm, not tempo".
And inheritance can be expressed in terms of composition, the base class(es) can be represented as composition and delegation instead (with a protected interface, a public interface and the implementation details all tightly coupled behind a single name which is what really makes it so poor).
I finally broke through it after being forced to write a ton of enterprise Java, which helped me come to realize that classes and objects are useful units of abstraction for encapsulating related sets of data and behavior, as well as finally grasping the idea of composition and the utility of things like dependency injection, but I could've avoided years of pain if someone had actually explained that properly with concrete examples of useful object-oriented code. Since then, I've seen the same conceptual misunderstandings over and over from beginners and all of it comes down to the fact that these inane `Cat extends Animal` examples are simply wrong.
> When you've never written a line of code in your life, abstract things like "network protocols" and "file I/O" and "ImageDisplay" (which is neither an image nor a display) are confusing, and so frustrating, and so not useful for teaching a complete novice.
The solution here is not to teach them something that dooms them to even more frustration and confusion in the future. Also, if someone has never written a line of code ever, why are you teaching them OOP? If someone can't yet grasp concepts like network protocols and file I/O, OOP is literally not useful to them. It's like teaching someone calculus when they haven't even learned algebra yet. Start with the basics and then build up from there.
I mostly agree in principle with the idea of building up a foundation. Going right into the deep end without decent guidance may leave one with horrible heuristics.
However, (IMO) a crawl->walk->run abstraction does not always work well. I hate to say "it's case by case," but pedagogy is not easy. Algebra, IMO, should be taught either near or with its uses (modern calculus being one of them). Otherwise, one may be learning a bunch of abstract concepts without much reification.
I derinitely agree pedagogy is hard. I have many thoughts about how the pedagogy of programming is deeply broken that are far too numerous to drop in an HN comment. I would say I'm not arguing for a purely linear approach so much as making sure there's actual conceptual understanding at each phase of the learning process. Teaching OOP before someone really grasps programming well enough to actually need it feels very premature, but there are lots of elements of it that can be sprinkled in throughout the learning process.
At first I was enthusiastic. Then I've realized that there might be better ways to solve problems than trying to fit everything in a "everything is an object" mentality.
OOP can lead to overly complex code, uneeeded abstractions and design patterns thrown on top of each other for no good reason.
There was a thinking that using objects you can model or imitate real life, but that is silly. And of course, programming doesn't deal with real life, but with bytes and instructions, data and actions performed on the data.
I still use OOP because most of the industry demands it, but when I can, I try to use a data oriented and a bit of a functional approach, even if I am using an OOP language.
Personally, after working with vanilla JS, then jquery, I looked at angular and react. I liked react so much better
I do agree with most of his points though, which just so happens to be closely aligned with how I've ended up programming. I like to use interfaces, so my objects hierarchies are very shallow and use a lot of delegation, overriding few if any methods.
I also write a lot of free-standing functions, some long, when I feel that's best. I also mix in a fair bit of functional-like programming, especially when massaging data.
I totally agree with you. I think he focuses on this style because it's generally what's taught in Universities, and he also has tons of tutorials geared towards students. So he's exposing his audience to the idea that some OOP principles are good, but overly adhering to the paradigm (in the way that would get you a 100% grade in an OOP class in college) is actually a bad way to program in general.
Being self-taught, I was already a proficient programmer in Delphi and C++ by the time I went to University. So to me OOP has always been "OOP when you need it".
Though now that you mention it, even by the time I started University I had a profound dislike for Java's "OO all the things" approach. It has not subsided...
"Modeling the world as OOP", OTOH, is a load of bull.
… we use different sets of building blocks for modelling the world and modelling the software …"
1994 "Designing Object Systems"
https://www.google.com/books/edition/Designing_Object_System...
This apparently goes back to part of the inspiration of Smalltalk (dataless programming). It enables you to create an ubiquitous language and radically improves your ability to structure a program in terms of reusable abstractions. It is fundamentally about hiding complexity and specificity on one end and only requiring or assuming what you _need_ to require or assume on the other.
In comparison to this, inheritance feels like a half baked implementation detail that got out of hand. Especially static inheritance hierarchies feel like the antithesis of object orientation. Pinning everything down and turning a codebase into immovable concrete.
- Alan Kay -
Other remarks from him about OOP are (paraphrased):
- OOP is a recursion on the notion of the computer itself
- The internet\Erlang is more OOP than java
His vision for OOP is something akin to distributed computing: Each object is a seperate computer with its own private memory that absolutely nobody can write to, it presents to the world an interface which is the set of all messages it can respond to (messages might or might not be asynchronous, and might or might not be unreliable in order,delivery,etc...). "Late Binding" because you have zero gaurantees about anything not explicitly stated in the object's contract, anything the object doesn't promise to respect will be violated, you better not depend on it. This is to give freedom to as much different implementation of the same interface as possible.
Two other analogies I encountered are
- Objects are like hardware ICs (their interfaces are the external pins and the type of signals the IC expects on them, their internals is the private hardware and signals that nobody is expected to inspect or modify)
- Objects are like seperate programs (their interfaces are the GUI/cmd the program presents and the set of all possible actions you can do, their internals are the executable code and in-memory data structures that nobody is expected to inspect or modify)
Inheritance actually precedes mature OOP, it was introduced way back in Simula-62, the proto-OOP language that wasn't a proper language as much as a simulation framework. Inheritance makes perfect sense in a simulation framework, because you're modelling a small world with a definite ontology that you can articulate and be sure of, the creators of Simula stumbled on it when they found they were repeatedly duplicating code verbatium. The analogies of Car<Vehicle and Cat<Animal make perfect sense, this is what inheritance is really made for, it's just that most real software isn't discrete event simulations with platonic ontologies.
If one squints hard enough, one could argue that we are continuing to move in his direction even so, slowly and in fits. But it's not because we have an Alay-Kay-OO language, and to the extent it is happening, it's not clear that it's happening at a level of abstraction that languages matter much with. When people use OO as a term today, it's a different definition.
Rust is a bit closer to the older OO ideal in that each object is its own private machine that encapsulates/protects its own state, and is late bound. The borrow checker allows the compiler to statically check that each use case of a given class is memory/threadsafe, and enforces basic state machines like "finish using this reference before calling next on the iterator", or "give up the ability to read() this network handle before transferring it to another thread". Modern OO completely fails to provide safety for those sorts of APIs (e.g. Java's ConcurrentModificationException is a compile time error in Rust)
On top of that base, you can build progressively more expressive OO-style API contracts, such as connection pools.
All programming paradigms can do that, you are not making any particularly salient point about OOP here.
OOP doesn't mean that everything needs to be an object, but inheritance and shared behaviors are concepts that map neatly between the real world and the programming world, which is why OOP has been such a resounding success.
We have refined this approach now (e.g. using delegation over inheritance), but these basic concepts of shared behavior and reuse remain.
If I need some functionality, I write a function and pass it a struct. I’ve yet to find a time when this approach is too limiting, or chaotic.
Even for interfaces, function pointers achieve that goal entirely.
I do miss type safe vectors, e.g templating. Thats above and beyond the most common thing I miss.
There's a certain conceptual and implementation elegance to OoO so I understand why it became popular. But in practice all the new languages moving away from it are doing so for good reason.
`thing.doWork(arg)`
`doWork(thing, arg)`
`doThingWork(thing, arg)`
The first example is marginally more readable IMO. The second one sucks from a human perspective because we don't immediately know (unlike the IDE) that the function signature only accepts Things. The last one addresses this but is even longer, and we still have to mentally unpack the args.
IMO there is not much profound about OOP, but it can lead to slightly more readable code if used appropriately.
Functions can be invoked in at least 3 different ways or a mixture of the three
1. Prefix - C style f(x,y,z)
2. Infix - x + y; x.f(y,z); a join b
3. Post fix- (x,y,z) $ f
Infix is the best style in most cases, OOP accidentally helped programmers use a more readable infix style.
A lot of the benefits of OOP can be had by allowing good infix invocation support and interface support in an imperative non OOP language. Might as well throw in generics.
Infix is tricky because it doesn't scale well to complex expressions. I think Python does it right with plenty of infix keywords a la "if thing not in myset: ...".
I can admit though that from an IDE point of view having auto-completion upon writing thing and typing the dot and seeing the results is very useful, whereas there is nothing (as far as I know) that comes even close in functional languages where the second example is more typical.
Long function invocations are an annoying consequence.
I feel like classic OOP is a mistake because you're binding data objects with functional objects. The latter being a collection of functions that performs operations on the former.
If you break them up you can have multiple widgets for dealing with a data object. Each of them with whatever dependencies they need and nothing else. I feel like this style of programing has become more important because it fits in the the idea of computing as an assembly line. Passing data through a data pipelines. Steps which often happen on different processes or machines.
Dependency injection is also really really useful for clean C code.
1994 "It should be clear to anyone that models of the world are completely different from models of software. The world does not consist of objects sending each other messages, and we would have to be seriously mesmerised by object jargon to believe that it does.
… we use different sets of building blocks for modelling the world and modelling the software …"
page 6 "Designing Object Systems"
https://www.google.com/books/edition/Designing_Object_System...
Yea, sure, we didn't gain anything.
Except milions of programmers being able to model complex real world into their software that powers almost everything now.
Even if it is "shitty" "hard to maintain"
then it at least works
I've seen it all. Prius extends Car extends vehicle extends motor extends ...
Right within the first five minutes of the interview.
But I also think these old "a bike extends a " tutorials were the most frustrating thing in the world and made no sense to me when I was trying to learn OOP in the first place. From my memory, I recall all the examples I could find were absolutely nonsense but blogs were full of posts with lots of non-real-world about connecting web requests things to a DB code and lots of "yeah it's just like a person is-a mammal which is-an animal which...".
It wasn't until I wrote some c++ and looked at how it worked in the heap and stack before I started to understand it better. Then later I learnt about SOLID and discovered that you almost never really want to use extension in practice (at least in the problems I worked on), except for things like interfaces in places you expect (and plan to have, and even better if you already do have) a reason for variations in sub-types that have the same contract, or need to cross system boundaries.
I almost always want to use composition. I will use extensions but this should be really specifically be selected for a few key points.
It takes a brave interview candidate to say "the best solution depends on the exact nature of the problem - and your problem doesn't make any sense so there's no valid solution".
> Prius extends Car extends vehicle extends motor extends
The problem isn't that it's too in-depth, or too overbuilt, that answer is as close to objectively wrong as you get on a pretty open ended question and I'd imagine only an extremely junior developer would come up with that.
For example, you could maybe argue that a model of car extends a specific car. I'd expect an experienced developer to favor composition over inheritance at that stage, but that's open to debate.
But if a Vehicle extends a motor, does a motor extend a piston? Does a wheel extend a lug nut?
At that point you're moving away from "arguably correct" to just plain failing to answer the question. It shows they don't know the difference between composition and inheritance.
> It takes a brave interview candidate to say "the best solution depends on the exact nature of the problem - and your problem doesn't make any sense so there's no valid solution".
You're free to ask questions, we're supposed to be trying to collaborate. I don't think it's "brave" to instead say "your problem doesn't make sense". Shows more hubris than anything.
How is it any different than a systems design question?
I got that a few times and it's a good sign I have a senior candidate.
The real-world use case is simple when you imagine that I want to get a JSON payload describing different car configurations, possibly for a "car configurator" on a sales website, or perhaps for a videogame where there are different cars with different attributes at random. Once I go from "make a car" to "give me some json" everyone started to see it's as a less fictitious problem.
Virtual functions are about implementation: a derived type that overrides a virtual function does so specifically in order to deliver a specialized implementation.
The degree to which your class's virtuals match its public interface is an exact measure of how bad that interface is, as an OO abstraction. If your class was doing enough work to earn its keep, it would be presenting an interface in terms the client wants to see. Those are implemented for variant internal representations by composing calls to one or more private virtual interfaces.
The above is true about OO subsystems. But not all uses of virtual functions are about OO. Virtual functions are a mechanism. Anywhere the mechanism is useful, it is OK to use it, OO or no OO.
Java offers no other organizational facilities than OO mechanisms, so there you have little choice but to use them everywhere. In problems not suited to OO solutions, use of virtual calls may have nothing to do with OO, and none of the OO rules need apply: if it works, it works.
Any language that offers only one kind of abstraction is a very poor and limited language. Calling it "pure" should not fool anybody. The world is complicated and demands many kinds of tools. Any one will only match certain aspects of certain problems.
And that brings me to a topic that's entirely different but also very relevant: job interviews bring interviewer-biases with them.
If you run into an old-school interviewer who would do exactly that "Prius extends Car extends Vehicle, etc." nonsense, but you don't know it, they would rate you negatively.
If you run into someone who is just in love with functional programming, you'll lose any OO implementation.
If you run into someone who doesn't like it when you ask questions, you'll lose. If you run into someone who doesn't like you asking questions they don't know the answer to, you lose.
And if you get sent a 3-hour long Hackerrank or Leetcode algorithmic test, everybody loses.
Tech job interviews are just horribly biased and the game is won if you read your audience correctly. And even then, if the other person is a racist, or just doesn't like your face, or had a bad day, or feels threatened, or disapproves of how you write "Javascript" instead of "JavaScript", you still lose.
The exact example the grandparent used was essentially a wrong answer, so our "old-school interviewer" (that sounds kind of ageist doesn't it?) marking us down for not using it is a bad thing.
An interviewer who loves functional programming and doesn't communicate any preference then marks you down for not reading their mind is someone to avoid.
An interviewer who punishes you for asking questions is a huge red flag and you'll be dodging a huge bullet.
I'm a self-taught dev so I definitely have some thoughts on how tech interviewing goes, but an interview runs both ways. I'd much rather miss out on a job because the other person was racist or they hate when people ask questions, than to end up working with them.
Similar works backwards, if you're using the interview to also ask the right questions, good people are generally not going to lie to you...
You'd think common sense would tell one not to define what's good for them in general based on what's good for you when they're "missing rent with an eviction and sleeping in your car".
I think this is a different challenge related to the "background" hidden context: training interviewers. I think the "foreground" discussion point ("please make me a car") is way less relevant than the skill of an interviewer.
For about a year I tried this with every candidate: literally tell them what I am looking for in the interview and what is on my scorecard. Nobody asked any questions even once.
So don't project programming ideals you don't believe in. If they can't reconcile different opinions, it's their problem. You won't have the conviction or experience to do it convincingly anyway.
That's actually a win in your book, not a loss!
You can’t add code to ducks.
You can’t refactor ducks.
Ducks don’t implement protocols.
If I got asked this question I'd probably spend the whole interview inquiring about the purpose of the modeling. It's for a simulation? What info do you want to get from the simulation? It's for an ERP app?I don't have problems with simple tasks which are not real-world but abstract something from the real-world use cases. The problem with "model me a car" is that it abstracts nothing.
Also for the most real-life purposes of modeling a car factory you wouldn't even have a "Car" in your object hierarchy.
It wasn't until I started trying to make a simple video game that these concepts made sense: an "Enemy" class could have properties of health and speed that a "Boss" or "Minion" could inherit from, etc.
I wish someone had told me this when I first started coding. I wasted so much time building pointless hierarchies based on ontology rather than DRY.
There are aspects of DRY that are worth pursuing (like reducing the number of places you need to get things right) and some that aren't worth pursuing (thinking you reduce the amount of work by re-using code heavily). For code to be reusable you have to be sure that developers only have to care about its interface. Implementation inheritance often doesn't do that. I've worked on projects with people who were obsessed with DRY and made heavy use of implementation inheritance, and the projects inevitably needed extra work to untangle a brittle mess with lots of surprising behaviors.
I speculate that if we think of our hierarchies as tools, they say at least as much about our own brains and the problems we’re trying to solve as the do about the domain.
Which is fine, tools exist as levers for the mind. But we shouldn’t confuse them with reality. The map is not the terrain.
Perhaps you mean — The analogy is…
The real art is maintaining the balance between abstraction and pragmatic simple code.
In fact, if you think of Go as an OO language (which I guess many won't), it has all the things you actually care about from OO. It doesn't have inheritance and you don't explicitly declare that a type implements some interface (). Since there is no inheritance of implementation you are forced to use composition, which is what people tend to end up recommending after a few years of fighting with OO code that has been written by people in love with a Linnean hierarchies and complex approaches to DRY.
() There are tricks to accomplish this, for instance creating a New() function that returns an interface type, which will make the compiler ensure you implement the interface).
Yeah. Exactly. Which is why Dog extends Animal. It has a set of common features (code/behaviors, and data like number of legs, amount of fur, and so on). It is a really good analogy and a really good point to give someone an idea of why you would use inheritance. It requires absolutely no programming knowledge, and best of all, it's not difficult to imagine cases where you might actually use it. c.f. basically any game with dogs in it where they're a subclass of NPCs.
And the same for Car extends Vehicle. A vehicle can have any number of wheels and any number of doors. A car can have any number of doors but it always has 4 wheels. All cars move in approximately the same way, while vehicles can articulate or skid-steer or ...
Just because something is simplified does not mean it has no value. And just because *you* would not use these designs in *your* life does not mean others wouldn't. As TFA says: "unless they are talking about writing a clone of The Sims".
https://en.m.wikipedia.org/wiki/Reliant_Robin
https://en.m.wikipedia.org/wiki/Piaggio_MP3
What shall we do with them in our class hierarchy?
Does that mean zero doors and zero wheels? Because a jet ski is surely a vehicle, right, and it has zero wheels and zero doors.
Likewise, what about a cable car (aerial lift)? It has doors (maybe), but probably not an internal engine. It's definitely a vehicle, but it has almost nothing that's similar about it compared to the car (automobile) you described earlier. If it where a class, the "vehicle class" could really only meaningfully have some attributes related to transport (like human/cargo capacity) and some methods around movement in the most general sense. If we keep breaking this down, we'll find a huge taxonomical network, a vast sea of interlocking Venn-diagrams of attributes and capabilities. This is the corner that those OOP tutorials force people into: trying to turn a network into a tree, and it doesn't ever really work (at least not without awkward compromises).
Sure, for a beginner who's never grappled with trying to map concepts to things, this can be a nice contained way to start. Eventually though, that beginner will need to learn some the next levels of more general thinking; e.g. one where they can start modeling interfaces/protocols and their relationships.
Except even in game design it's a bad idea once you get more than a handful of entity types. https://gameprogrammingpatterns.com/type-object.html
What I got as a side-effect was a bunch of re-written code. And I wondered:
Maybe OOP is a terrible paradigm. Maybe the designs that come out of it are so awful that it causes people to rewrite code, over and over. Re-examined systems are often better than first attempts. Once you reimplement something five or six times, you've probably explored most of the problem space and are pretty good at it. It's even likely that you are thoroughly sick of the project and just want to get the stupid thing done and shipped. (Yeah, your PMs and entire management chain just smiled behind your back).
In other words, it's Brook's "plan to throw one away, you will anyway" in action, while telling your boss that you had to rewrite your framework again because it didn't handle the flightless, swimming bird scenarios.
So it helps to compare the old ways to the new ways side by side, with a real world example, to understand why the new ways are better.
It's like listening to a conversation about cars where someone says they hate cars because you have to figure out a manual choke and the many inner tube failures.
Do people still use these crazy inheritance trees? I haven't encountered it since the early 2000's.
EventTarget -> Node -> Element -> SVGElement -> SVGGraphicsElement -> SVGTextContentElement -> SVGTextPositioningElement -> SVGTSpanElement
And the whole tree could be described as "crazy", but it does actually make sense.But inheritance is just a shit feature imo. Situations where it would be beneficial are extremely rare. I generally just pretend it doesnt exist. There are much better alternatives to it for most situations (eg. composition, build a class from other smaller classes that do one thing)
I think this is also the better approach, making inheritance something you have to explicitly design for instead of a way to monkeypatch almost everything.
You can with "new", no?
public class Foo
{
public void Say() { Console.WriteLine("Foo!"); }
public void Think() { Console.WriteLine("I;m thinkin about thos beans");
}
public class Bar : Foo
{
new public void Say() { Console.WriteLine("Actually, Bar!"); }
}
So with Bar we have inherited Think(), but have overridden Say(). Ok technically the MSDN docs will say that we've "hidden" the inherited method Say(), but the effect here is that we've overridden something not marked "virtual"edit: Oops yeah I'm talking rubbish, see replies for more info :D
Once I have all these really nice tiny functions, Its trivial to reorganise and replace them.
Though I find classes also very useful, a class is mainly a bucket that you put functions into. Ideally a class is a black box that does one thing. All the functions needed to do that thing are in the class and also any relevant fields. Not being able to have fields in there would be counter productive. It's just so handy to have all the functions + fields that govern this one element of your game all in one place.. When you need to make changes everything relevant is one place, and that place also functions as a black box that the other elements dont know about. So to change one feature in the game u just change or replace that one class.
Just looking at the functions, nice maintainable code would look identical in a functional or OO language. I think the big differences between functional and OO are actually not important. I could put functions in a class, or just together in the same file. Putting certain functions and fields into this same bucket is just a convenience and its intuitive to understand imo. Like I said in 6 months time when I have to change feature X I know where to look.
It's clear that OOP in the sense of "Car extends Vehicle" is not a good idea, yet the classic OOP languages go out of their way to support this kind of programming. Meanwhile the alternative approaches require you to jump through a bunch of hoops or apply convoluted "design patterns". Writing good, modern "OOP" you are often fighting the OOP language! Things are getting somewhat better (e.g. Java Record types) but perhaps it is time to admit these languages just aren't a good fit?
As an example, if I want to use composition in C# using ASP.NET, I simply inject the class I want to use into my new class using the built-in DI. This is as simple as extending the class.
- Discriminated unions
- Immutable data types (records)
- Free functions (or modules of free functions)
- Software transactional memory (STM), atoms
- Ad-hoc polymorphism (traits)
- Custom operators (used sparingly for building DSLs)
- List comprehensions
Instead we get anti-features like automatic properties, implementation inheritance, events, object initializers, etc...
there are
>- List comprehensions
Enumerable.Range?
>- Custom operators (used sparingly for building DSLs)
Could you please show what kind of DSL you'd want to write?
Agreed, although this a recent addition in Java and a step away from old school OOP
>- List comprehensions
Enumerable.Range is not equivalent to list comprehensions. For example, in F# you can build a list using a comprehension containing more or less any code you like. It's incredibly powerful for things like building HTML.
>- Custom operators (used sparingly for building DSLs)
These are used in many places, but to give one example https://www.cs.tufts.edu/~nr/cs257/archive/simon-peyton-jone...
Add on the fact that people somehow seem to think Alan Kay has some insights (even if they can never tell you what they are), and Bob's your uncle.
What do you mean by "delegation" here?
> Until recently non-OO languages had very little support for delegation
No they don't. Show me how you'd do that in e.g. Haskell. And note that Rust has `delegate` as a macro even though it has a trait system.
Or with a record you just explicitly implement typeclass methods in reference to the contained data.
If you mean doing it without doing a typeclass, well the functions need to have different names. If that’s exactly what you want. In order to have the same function name you need a typeclass.
Deriving sort of does what delegation does, but in a very limited way, and even when it does work you can't use it for user-defined typeclasses, so it's not a solution to the problem except in a handful of special cases.
> Or with a record you just explicitly implement typeclass methods in reference to the contained data.
The whole point of a delegation feature is that you don't have to do this explicitly for each method.
It's not a bad base for UI programming. There is quite a lot of functionality that is suited to the OOP paradigm.
As a dev you just use whatever features u like and can safely ignore the majority of them. Its very non-prescriptive unlike some languages where you must do a thing only one way.
Eg. Inheritance is pretty much terrible, I just never use it. Same way I try to not write bad code and instead write good code.
Also sure you need to put functions in classes, but thats fine, classes are just buckets for code. In any language you need to put your functions somewhere eg. in a text file.
* That has a hardcoded list of strings baked into it
* Accepts as inputs a filename (but can be empty) and a substring to search for
* At runtime, either (a) (if filename empty) iterates over hardcoded list of strings, or (b) opens file and iterates over lines in it
* For each string / line, if contains search string then output to stdout (and maybe also increment a counter).
It's very artificial but does allow the basic idea of OO to be illustrated and it's far less code than a GUI example. You have a base class and two derived classes that really do have a different mechanism, and even have different state (file handle or integer index). The program logic is also quite decoupled (the class interface is not tied to the fact you're doing string search).
You could solve the problem without classes by reading the whole file into the same list structure as the hardcoded strings, but that aspect is actually quite nice because you can present that first and then ask what to do if you want to avoid that unnecessary memory usage. It's also nice that you're illustrating a really common OO pattern (iterator).
The only slight downside is that some languages make this easy to do without (explicit) classes e.g. Python generator functions. But you can add a footnote that they exist and this is just to illustrate classes - it still beats the abstract examples.
static void Main(string[] args) {
var filename = args[0];
var needle = args[1];
var linesToProcess = filename != '' ? File.ReadAllLines(filename) : HardcodedLines;
ProcessLines(linesToProcess, needle);
}
static void ProcesLines(string[] linesToProcess, string needle) {
// lines-processing code...
}
into something like static void Main(string[] args) {
var filename = args[0];
var needle = args[1];
var linesProvider = filename != '' ? FileLinesProvider(filename) : MemoryLinesProvider(HardcodedLines);
ProcessLines(lineProvider, needle);
}
static void ProcesLines(ILinesProvider linesProvider, string needle) {
var linesToProcess = linesProvider.GetLines();
// lines-processing code...
}
would still need some justification, IMO: what's the benefit of giving ILinesProvider to the ProcessLines instead of just string[]?If your GetLines() is returning a string[] then I agree there was no benefit. But that's not what I meant. As I said, I'm imagining the base class (ILinesProvider) to have an iterator-like interface. So, rather than string[] GetLines() method, it would have a string getNextLine() method that you call in a loop. That way, with the file, you don't load the whole thing in memory (as I also said).
If your GetLines() method is returning an IEnumerable<string> (since this seems to be C#) then this is the problem I mentioned at the end of my comment - the base class I'm imagining is similar to an existing base class in the language, and it's confusing to write your own similar-but-slightly-different version in an example. But I think it's best to do it anyway and explain it away in a footnote. (But my ILinesProvider wouldn't return an IEnumerable, like in your snippet - instead, ILinesProvider actually is the iterator (but with a slightly different interface).)
The suggestion of GUI widgets is certainly better and more concrete; although it's skating dangerously close to "Circle extends Ellipse" and "Square extends Rectangle" territory! (e.g. https://softwareengineering.stackexchange.com/questions/2381... )
- A base class and its implementation(s) must never be written by the same person.
Obviously a bit of a strong opinion, but inheritance should delimit layers of abstraction, technically separating the responsibility of developers.
Yep. That would be like having immutable lists which implement List which has an "add" method. Obviously that would be too stupid for a sensible programming language.
Compare:
> If B and C are subtypes of A, then List<T> must be invariant over T since List<B> should not be a subtype of List<A>, which can contain both B and C.
with:
> List<Cat> can't become List<Animal>, because you might add Dogs to that list!
I've thought about this for quite some time and couldn't come up with anything that strikes a nice balance between simple and actually useful.
I admit I taught intro to C++ and used shapes as an example. I spent a few weeks building a little "ASCII render" thingy with a 80x30 "canvas" where each character is a pixel. Then you could place Shapes on the canvas and the shape would decide whether a given pixel was contained() inside of it.
I remember my first OO class where the car analogy was proven to be bad using the existence of the El Camino.
Goodbye, shitty Car extends Vehicle object-orientation tutorial (2011) - https://news.ycombinator.com/item?id=21298341 - Oct 2019 (101 comments)
Goodbye, shitty "Car extends Vehicle" object-orientation tutorial - https://news.ycombinator.com/item?id=2914405 - Aug 2011 (128 comments)
Inheritance in OOP languages is used to either for interfaces, or for tacking on data and maybe functionality because you are too lazy to implement out composition instead.
"You can’t fake the ability to turn a duck into a penguin by moving its duckness into an animal of some other species that can be replaced at runtime."
See, for example, section 3.2 in https://www.econstor.eu/obitstream/10419/66819/1/717744094.p...
Modern multi-paradigm langues do a much better job of letting you use OO when OO works, functional when functional works, etc.
You should use the tool that works when it works. I do plenty of functional and OO programming in ruby, declarative programming in SQL, mostly functional style in JS. There are plenty of places where each one works great.
OOP wasn't meant to be used by professional programmers ever, but this is always always forgotten. Why do we still talk about OOP when it comes to professional / full-time programming? OOP was always meant to be about providing end-users a way to create programs & dynamic behavior without becoming full-time programmers.
- http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay...