Don't Distract New Programmers with OOP
prog21.dadgum.com
prog21.dadgum.com
For example, suppose I'm a new programmer given the notional task of making a program to make me a sandwich, can I just:
makeSandwich();
and be done with it?Or do I need to break it down to the n-th degree?
//make bread
bread=harvestwheat();
bread=millwheat();
bread=bread+water;
//and so on
is that the right way to even make bread? Or do I already have bread I can work with someplace?Do I?
Sandwich=bread+jam+butter+bread;
Or Sandwich=Sandwich.add(bread).add(jam).add(butter).add(bread);
Or do I need to do something else? Maybe this particular programming environment doesn't need me to make bread, but I have to transform a milk object into butter for some reason that seems entirely random to the new programmer. Who knows? Lots of the things that we take for granted seem entirely arbitrary to the new programmer and learning to break a problem down in a way that maps to the environment is a very hard problem to learn to solve repeatedly.edit in a way this is a common topic even on HN with the "do x in n number of line" posts that are common.
Describe, at high level, what needs to be done to make a sandwitch, in your head. Then write code that looks exactly like what you said in your head, as much as possible. Dont worry if it turns out to be impossible to implement that way - you can always tweak it later until it becomes possible. Just try to make sure it reads more or less clearly.
Repeat with each of the steps, checking whether there already is a library that does that for you.
function makeSandwich() {
var breadSlices = getBreadSlices(2);
var ham = getHam();
var butter = getButter();
var butteredSlice = spread(breadSlices.first(), butter);
return foodPile(butteredSlice, ham,
cheese, breadSlices.last());
}
Continue the same way with getBreadSlices, getHam, getButter, pile.As you progress this way, you'll notice that you'll be able to implement some processes in terms of more generic functions (e.g. spread and foodPile).
You'll also notice that you need an environment (macro, such as kitchen, or micro, such as frying pan) to temporarily place stuff that you're working with, while you wait for some process to complete. Thats when you start creating classes and objects that represent this environment.
Real code example:
https://github.com/jlipps/async-showcase/tree/master/promise...
One of the best ways I ever came across to break down a problem.
In the UK, standard practice is to butter sandwiches on the inside, ie on the surfaces of the bread which interfaces with the filling. Are you by any chance talking about buttering the outside?
I think a lot of it comes down to the fact that most sandwiches these days are prepared and then eaten right away, whereas I was brought up in a world where sandwiches tended to be prepared long in advance of being consumed.
That said, Americans will cheerfully slather "mayonnaise" on bread.
I thought garlic fries, like the sort at Giants games, were delicious! Some garlic, olive oil, parmesan, etc. I was shocked to discover that here and in the UK garlic chips consist of chips (fries) with garlic-accented mayonnaise spread all over them.
And don't call it Aoli. That's just dressing up a disgusting condiment with a fancy word.
I use butter and mayo for one of two things: flavour (eg I love kerry gold on toast ;-) often nothing else) or moisture - but if I don't need the moisture, usually because I've got tomato, peppers, coleslaw, relish or something else providing that, I will leave butter and mayo out. Unless I want them for the flavour. (As an side, I don't understand why people would ever want butter on a breakfast roll)
These are precisely the instances in which I would want something oily (butter, marg or mayo) as waterproofing for the bread. (Mustard also works, if it's appropriate for the sandwich.) "Everyone has the gout," as they (don't actually) say in French.
I just don't need a peanut butter and butter and jelly sandwich.
Since then I've looked at the ingredient list of every other product with great suspicion, expecting to find mayo everwhere...
In Russia, where I grew up this is the only way to make a sandwich.
Of course I also grew up in very immigrant heavy areas so that may have been part of it.
example story: as a user I would like a form page that take an address I enter and renders a google map of it
High level overview:
var form = renderForm();
form.onAddressEntered(function (address) {
renderMap(address)
});
How do I show a form? Ah, I do that part in HTML, so no need to renderForm() form.onAddressEntered(function (address) {
renderMap(address)
});
How do I access the form? Hm, its not an object. Maybe I should make it one, maybe not. I'll simplify this time, as I don't know what the future will bring: onAddressEntered(function (address) {
renderMap(address)
});
Okay, so how do I run something when an address is entered? (reading, experimental code in REPL). Right, so the best way is apparently by attaching to the submit handler of a form. Oh, and I also learned that the handler takes an event argument and I need to call e.preventDefault() function onFormSubmit(form, listener) {
document.getElementById(form)
.addEventListener(function(e) {
e.preventDefault();
listener(e);
})
}
Lets modify the original code then onFormSubmit('address', function (address) {
renderMap(address)
});
Wait, how do I get the address?Lets see if there is a pre-made function to serialize a form. (looks up) Ah, found it.
Lets modify onFormSubmit again. It should now add an event listener to the selected form which serializes the form and prevents the default submit event.
function onFormSubmit(form, listener) {
var form = document.getElementById(form);
form.addEventListener(function(e) {
var data = serializeForm(form);
e.preventDefault(); // which is submit
listener(data);
})
}
Back to the original code onFormSubmit('addressForm', function (data) {
// lets make sure we got that first part right
console.log(data.address);
renderMap(data.address)
});
In the process, a neat utility for forms came out - a function that attaches a submit listener that gets all the form data.So how do I render a map? Etcetera.
I think its important to always have a written-in-code high level overview of the process, even if it turns out that the current high-level overview is potentially wrong. You can always change it as your understanding (of both the process and the tools) grows. But at the end, that overview becomes your code, and the end result will unavoidably end up as understandable as your current understanding of the entire thing.
The hardest part of this process is to stop worrying about getting all the details right (at the beginning) and only focus on whether the code you're writing is describing your understanding of the problem well.
In Java, you're forced to use OOP for every problem. No beginner understands why to define "hello, world", you also need a class, and a 'static' method on that class, and to invoke a weird-looking special class called "System".
It isn't obvious why there are "String[]" types and "ArrayList" types, and why I should care about the difference. Why does "main()" take "String[]" yet I am encouraged to use "ArrayList" in my code? This is just a sampling of difficult-to-answer questions that a beginner encounters in using Java that don't have a grounding in computer science, but just in Java.
It just so happens that among design choices, the choice to implement your Python code using OOP is a trade-off. It always is, but Python makes the trade-off clear as day. The win is increased ability to implement interfaces, language protocols, inheritance, polymorphism, the ability to bundle state and behavior, and the ability to bundle related behavior together. The loss is reduced readability and reduced beauty. Since these latter things are counter to The Zen of Python (PEP 20), idiomatic Python programmers will tend to torture themselves over OOP usage, to the point where only data structures that truly demand OOP's features come into existence.
This means that beginners will more often see a useful_function than a UsefulClass in Python's stdlib and associated ecosystem, and this is A Good Thing, since functions are much more composable than classes are (not to mention simpler).
I think Python will therefore also teach beginners vigilance in the use of classes, which the entire software community could use a little more of!
1. function definitions being constrained to classes has nothing to do with OOP
2. static functions or members have absolutely nothing to do with OOP
I see a lot of criticism of OOP, but when I get to reading the details, it's all about Java's flavor of OOP.
What is a class or static method? These are OO constructs and require additional mental overhead when learning.
You don't need to pass parameters to your car to start it. You don't need to force yourself to only focus on the recipe (scope) when making dinner.
None of these things map very well with the real world and could also be considered additional mental overhead when learning. You don't need to know how to code to learn best known practices in building software.
Learn things one at a time; "best practices" can wait til you can demonstrate why it is a best practice. Why should I make a function? Until you actually face the problems that abstractions are used to solve, learning them is confusing and difficult.
This also happens in math quite a bit. There is a knowledge gap when teaching concepts like derivatives for the first time. So we are told to initially ignore details while learning.
In my experience, it's best not to delay learning the details. You don't have a real understanding of something until you understand the details.
However that makes even less of a case for Python.
1. Instantiate Object.
2. Invoke Method.
3. Get or modify attribute.
4. Do some introspection (even if the term is not used)
Most importantly, learning how to define their own classes isn't really important for beginners. 1. What is an object?
2. What does it mean to "instantiate" an object?
3. What is a method?
4. What does it mean to "invoke" a method?
5. What is an attribute?
6. What does it mean to "get" an attribute?
7. What does it mean to "modify" an attribute?
8. What is introspection?
And so on, until we run into a concept through which we can explain something. The idea of an "object" is a big hurdle for beginners, especially if they don't have experience writing code — it seems like weird boilerplate. Likewise, "introspection" and other meta-concepts are incredibly challenging for beginners because they have a hard time stepping "out of the code," so to speak.Here is an example that instantiates an object, introspects, gets an attribute, introspects again, and invokes a method. It's 11 commands.
>>> import requests
>>> def introspect(obj):
... for x in dir(obj):
... print x
...
>>> request_object = requests.get('https://news.ycombinator.com/item?id=6919275')
>>> introspect(request_object)
__bool__
__class__
__delattr__
__dict__
[...]
request
status_code
text
url
>>> request_object.status_code
200
>>> request_object.url
u'https://news.ycombinator.com/item?id=6919275'
>>> introspect(request_object.url)
__add__
__class__
__contains__
__delattr__
[...]
title
translate
upper
zfill
>>> request_object.url.upper
<built-in method upper of unicode object at 0x29a3330>
>>> request_object.url.upper()
u'HTTPS://NEWS.YCOMBINATOR.COM/ITEM?ID=6919275'
After enough practice using objects from various python libraries, in the course of doing functional or imperative programming, they will have an intuitive understanding of objects and what they are for.Some interested students might ask "What's an object, under the hood?" and "how can I create my own classes?" That's when you can introduce them to classes (http://docs.python.org/2/tutorial/classes.html). If they're fine with the intuitive understanding that comes with performing examples like the one above, leave them be. Don't inflict OOP principles they don't need to know yet.
I think the example code you gave is fine, but does not actually answer those questions. The mind of a beginner is like a map of a country with unmarked or incorrectly-marked territories. They don't know where the relevant boundaries between concepts/territories are or even how to begin drawing them. They can't differentiate between important and unimportant details. Like a child who is just learning how to speak, they see the differences in everything and every difference matters. That is, to a beginner, if there's a difference it's important.
Here's a Ruby example. I've given many students this code:
my_name = "Jesse"
my_age = 30
puts "Hello! My name is #{my_name} and I am #{my_age} years old."
and had them conclude without prompting, "Oh, you can name variables anything you want as long as they start with my_." They will not know they're forming this belief and will carry it with them until something — a teacher, another bit of code, etc. — contradicts that belief, except that when the contradiction arrives they may not realize consciously they had formed this particular incorrect belief.When I look at your code, I see a few dozen opportunities for students to form exactly these kinds of beliefs, which is one of the challenges of writing effective curriculum. This is doubly difficult when there isn't a teacher nearby to catch these mistaken beliefs as they're formed or shortly thereafter. In most online or "single player" learning experiences students will form a web of incoherent, confusing beliefs, not even know they're doing it, and carry on for much longer than they would otherwise.
For example, I'm assuming the "u" prefix on those strings in Python is a way to indicate that they are unicode strings. If a student already knows that strings exist and sees that output, it's likely that a large minority of students will assume that u'HTTPS://NEWS.YCOMBINATOR.COM/ITEM?ID=6919275' does NOT represent a string but something else entirely. They will then carve out a hole in their schema of the world where "u-things" go and it will never occur to them that doing something like
request_object.url == 'https://news.ycombinator.com/item?id=6919275'
is even possible.Maybe we're saying the same thing, but I hope you get my point. This has to do with the particular way most beginners in any subject interact with new information.
Probably, my example was off-the-cuff, and I think I may have misinterpreted the original intent of your post a bit. I'm still not sure which "side" of the discussion you're on, though. (Teach classes and OOP sooner vs. later). For my part I definitely favor "leave it for later"
In Zed Shaw's "Learn Python The Hard Way" book, after raving about dictionaries in section 39, he barely uses them for the remainder of the book, while spending a lot of time on classes and class heirarchies. It's a succinct (and very successful, I believe) introduction to OOP and Python's features so I can't vehemently criticize it, but I do wonder what the last few chapters would have looked like with a more flat function + dictionary approach.
I understand the issue of little details stuff, for sure. I've never taught python but I have taught bash. Gave you an upvode for for the second paragraph, I found it to be an unusually eloquent way to describe the learning experience. I've never been able to express that idea so well.
You're right: static methods are not an OOP construct. They're a hack to allow the user to break out of the straitjacket at a specific moment, because it's impossible to write anything but a library otherwise.
The issue, thus, is that Java requires a beginner to learn a paradigm-breaking manuever as a basic operation. "Java is a glorious OOP language. If you want to write anything that's pure Java, though, here's how you stop being OOP in order to do so."
(Also, for the record: I like OOP. I like Ruby specifically because it makes OOP so natural and honest.)
I like how Rubyists/Pythonists jump to criticize Java, but they don't know the first thing about OOP.
I don't even use Java but these critiques are retarded, seriously.
Technically, such methods are not methods. They are functions. And the distinction between a "static method" and a function is completely arbitrary, dictated by a completely arbitrary limitation of how bytecode is organized on top of the JVM (e.g. class-files instead of package files) and by somebody's flawed vision that all function declarations should happen inside a class, even if those functions are not methods.
And actually, what I've said in my first sentence is wrong. "this" may not be passed to static methods, as classes are not objects, however classes are some sort of pseudo-objects that are special for the JVM and "this" does exist in certain contexts, like when declaring a "static synchronized" method, which does use this pseudo-object for acquiring an intrinsic lock. Also this pseudo-object does have a constructor.
So you see, classes play the role of packages for static members and methods, except that in a language like Python, packages are objects themselves, whereas in Java they aren't. Plus, if you look at classes with static members as being packages, they are retarded packages, because you can't pass them around as values, you can't override imports and so on.
I can keep going btw.
I agree that they are just functions, but no one is trying to "hack" or to break encapsulation. They could have used packages instead, yes... But having them inside classes is pretty convenient as this allows us to have a better syntax.
Oh and not being able to pass classes around is a language-specific thing, so you can't really use that as an argument against static methods.
You say it's about "Java's flavor of OOP." That's 100% true. So, how do you explain Java's flavor of OOP — which is necessary to explain why Java does certain things that would otherwise seem like arbitrary invocations — without explaining OOP at least in part? As soon as the beginner sees the word "class" or "public" they'll be forced to either contextualize those concepts or take them as arbitrary strings one puts into their program because Magic™.
The OC's point was that Java forces people to grapple with certain OOP concepts, even if those concepts are particular to Java's flavor of OOP.
You're both just talking past each other.
It doesn't?
JavaScript, for example, is an OOP language without classes per se. Indeed, many JavaScript programmers avoid what little built-in class-like behavior JavaScript has and implement their own class definition/inheritance/instantiation mechanisms via things like Object.extend.
And although it's rare, one can define "singleton" methods directly on objects in Ruby, too:
animal = "Dog"
# This is now defined only on the animal object
# and no other String instance
def animal.bark!
"Woof woof!"
end
animal # => "Dog"
animal.class # => "String"
animal.bark! # => "Woof woof!"
So, JavaScript is an example of an OOP language without classes at all (or at least one where you often pretend they don't exist) and Ruby is an example of an OOP language which has classes but where function definitions are not constrained to classes.This is the point I understood the GP to be making.
The OP actually did a very clear job of distinguishing between Python- and Java-style OOP. I think you're the one cherry-picking his Java example and then extrapolating that to refer to all of OOP.
?
Even if that had flaws, PyQt carries over all the Qt abstract classes just fine.
Every language has a design goal. Python doesn't care about interface contracts. The "one correct way" is the contract.
It's almost an entirely distinct problem domain. Besides, what would a contract be in a dynamically typed language?
I don't think these features are bad, but they're not appropriate everywhere.
Yeah don't worry, I'm not gonna use Python.
> Besides, what would a contract be in a dynamically typed language?
Being dynamic has nothing to do with this. See: Clojure, PHP, etc. They have contracts.
Of course things might not be appropriate everywhere. But with Python/Ruby you don't have a choice, they just lack these features.
I'd say even that Abstract Classes are a horrible hack to the braindead inflexibility of languages like Java
And 99% of the time you're doing them in Python, you're doing it wrong
"You'd be crazy to use that"
"Um, OK"
"Often, I use it"
"Everyone must be forced to exclusively use it"
"Sometimes its sucks, but its the only sane thing to do"
And then right back to start. Endlessly. Forever.
So in 2014 OO is rapidly nearing the "you're nuts" and I see functional is rapidly nearing the "I use it" with, inevitably, "Everyone must be forced to exclusively use it" coming up soon. The relevant point is, have we started preparing for functional fundamentalism or whatever you want to call it? Also have we started scrubbing our resumes and githubs to remove the stain of OO programming?
Besides, the state in which we are in is rather healthy. Back in the late nineties, early 2000, everybody was into class-based designs described with UML.
And I do wish people were forced to use functional programming, because that's the only way many people end up learning something new. OOP is not the best choice for every problem. Sometimes a function is better than an ExecutorManager. Sometimes a Map or a type-class are better than an inheritance chain. Sometimes a monad is better for manipulating data streams instead of iterators. And so on and so forth.
Also, the Gang of Four is seriously outdated.
UML is great as collaboration mechanism at the enterprise level across sites.
> Also, the Gang of Four is seriously outdated.
Still largely unknown on the enterprise, sadly.
I am also not sure if programming trends follow circular time. I haven't seen logic programming making a big return and OO and FP are still in its first cycle. It just took FP ages to go from stage 1 to 3 (outside niches).
FP is far more clear a concept than OOP. OOP has no real fundamentals apart from encapsulation, which really isn't specific to OOP. OOP is whatever haphazard setup a language designer feels like using. FP concepts (like the ones you mentioned) are pretty clear across languages.
FP is certainly in its first cycle; ML just keeps going on. Industry is only now catching on.
- DO teach encapsulation of state.
- DO NEVER ever try to teach about objects as representations of real-world things. And for the love of god, do not give out modelling assignments where "Human inherits mammal" is an answer.
- DO start with collections: Vector, List, Stack etc. are easy to implement and showcase the virtues of encapsulating.
- DO teach polymorphism, but only in terms of interface inheritance. The collections you have taught before are a good starting point: ICollection, IRandomAccessable etc.
- DO NEVER EVER talk about class inheritance. Do not even use the word inheritance. Call it "is a relation" or something like that.
- When the time comes to teach inheritance, emphasize the Liskov substitution principle.
Human isa mammal (let's say). Human is a Liskov substitute in any context requiring a mammal. What's the problem here? Is it just the fact that Mammal brings to mind a classification system which happens to be hierarchical, and you're afraid people will become too accustomed to hierarchies?
This doesn't work for several reasons:
* In the real world there are multiple overlapping hierarchies, which have different uses. Human 'isa' SentientBeing as well, which may be a much better heirarchy for modelling communications with aliens and robots. Most OOP languages either don't handle this at all, or handle it badly.
* Your hierarchy will almost certainly have to change (you'll be wrong, or new requirements will come along). Frequent major code breakage / refactoring is the likely result. If you also need to persist data across versions, you're in big trouble.
Property / prototype based methods are in general much more flexible for real world data.
There may well be overlapping hierarchies, and trying to capture taxonomy with an adequate analogy to explain the concepts of inheritance to somebody is perhaps not the best way. But to say that you must never enforce an arbitrary fixed hierarchy contradicts the very purpose of inheritance in a good design.
For your particular example the correct way is to have a separate interface called "ISentient" that captures whatever methods and fields are required for sentient communication.
Most OOP languages support that concept.
So you can have a Human that inherits from Mammal, but only Human introduces the ISentient interface. Your "Robot" can then inherit from "Machine" and also introduce ISentient.
A much better way is to use interfaces for most things: use IMammal then override "procreate" so you can account for specie extrema like the duck-billed platypus class, which is a mammal that lays eggs.
New programmers will only try to build deep inheritance trees if they are told that's what they're supposed to do.
I have to disagree with this. I think this is exactly how to think about this.
I once explained the shift to OO to an older gent who was familiar, not just with ancient scientific programming, but also with building a house. I told him that the idea was that code was so large and complicated that it was divided up among teams with each team getting specific parameters that their code had to fit. He said, well of course.
Then I said it was much like constructing a house. You had to put the frame up, and know where the windows and doors would be, but the details of the windows and doors could be decided later. Every door had to be able to respond to an "open" command and a "close" command, some may have a "lock" command, and so on, but the builder of the door worried about getting that stuff right. All you had to specify were the dimensions and the direction of the door.
Now this was a pretty technical person- and perhaps doesn't qualify as a beginner, but I find, in general, real world metaphors to be powerful for teaching.
IMO, this is the first mistake. Code should not be large and complicated; when it is, few things can save you, whether it be the language or paradigm you're programming in, or the structure of the team doing the programming. OOP is a solution to the wrong problem.
The core concept of OOP is adding dynamic method-based dispatching to your abstract data types but I would argue that while this is useful a lot of times, its not really fundamental. IN fact, there as many cases when dynamic dispatching will not be able to solve the problem on its oown: a good example is generics in Java and C# - parametric polymorphism helps enforce abstractions but isn't really object oriented.
The fact that we are still not using the high-level languages we have available to us. Vast amounts of new C, C++, and Java code are written each year. We are solving bigger problems, but we are using languages that are still catching up to the state-of-the-art of the 1970s.
That just uses different words to describe the same net result and the same net problem. You still have to find a way to make all of those pieces work together to do something complex.
But that wouldn't be glamorous, would it? Besides, this approach will make you hopelessly unemployable in today's world.
One person writing all the code for a system from the bare metal up is pretty much limited to embedded systems now.
As kazagistar succinctly puts it, "Inheritance is not taxonomy." Your house analogy isn't taxonomy; that's why it's correct.
> The heart of the matter is that people begin with the concrete, and move to the abstract. Humans are very good at pattern recognition, so this is a natural progression. By examining concrete objects in detail, one begins to notice similarities and patterns, until one comes to understand on a more abstract, intuitive level. This is why it’s such good pedagogical practice to demonstrate examples of concepts you are trying to teach. It’s particularly important to note that this process doesn’t change even when one is presented with the abstraction up front! For example, when presented with a mathematical definition for the first time, most people (me included) don’t “get it” immediately: it is only after examining some specific instances of the definition, and working through the implications of the definition in detail, that one begins to appreciate the definition and gain an understanding of what it “really says.”
Inheritance is not taxonomy. If you want to teach inheritance, show a couple of realistic examples of code which, when you add inheritance, becomes clearly better. We can see the pattern; we can make our own intuitive, abstractive jumps.
I think that you still have the wrong abstraction here. If you are looking for a general solution, it should be one that can handle LCR circuits, mass-spring systems, etc., though that is probably too much for an introductory course. My real point is that there is nothing to be gained by modeling circuit components or even circuits as objects -- you do not improve maintainability, you do not encourage code reuse, and you do increase the amount of code that does nothing to solve the problem.
Then I still find your point unclear. Why would you choose them as an exercise to introduce classes, which is what we were talking about? I agree, it's odd. Mammals and dogs, however, are a concept people can understand. As are windows and doors.
Further, "dogs and mammals" or "windows and doors" are extremely contrived examples. The fact that you have to invoke an artificial problem just to illustrate this point should be an indication that you are doing something wrong. On the other hand, simulating an LCR circuit is a real-world problem and one that students will probably be asked to solve as a homework assignment (e.g. in a differential equations course).
And mammals and dogs as a concrete example- a metaphor, if you will- is an example of a powerful way to teach abstract ideas, not an indication that something is wrong.
What I would find enlightening is how the mammal-dog relationship fails, not how there is something out there for which the model may not fit.
That directly contradicts how SICP teaches it:
Our computational model of a circuit will be composed of objects that correspond to the elementary components from which the circuit is constructed. (p. 273, 2nd ed.)
Elsewhere they say:
If we have been successful in our system organization, then to add a new feature or debug an old one we will have to work on only a localized part of the system. (p. 217)
... which sounds a lot like the benefits you deny. Do you really think SICP is wrong to teach this way? I don't.
I quoted a more general passage where they introduce object modeling here: https://news.ycombinator.com/item?id=6910292. But it seems striking that you would have brought up circuit simulation as when not to do object modeling, as it is the very domain they use to illustrate the strength of the approach.
Then we went on to create a dating app where it would retrieve things like "all the people with brown eyes"
I agree, with the caveat that there should be good discussion about when it's important to encapsulate state, rather than a rather blanket "always encapsulate data and methods" which is how it's often presented.
I hear this from time to time—it seems bizarre. Representing real-world things as objects is a fine design approach for many problems and is well worth teaching. Where on earth did the idea that it isn't get started?
SICP, a classic of programming pedagogy if ever there was one, has this to say:
One powerful design strategy, which is particularly appropriate to the construction of programs for modeling physical systems, is to base the structure of our programs on the structure of the system being modeled. For each object in the system, we construct a corresponding computational object. For each system action, we define a symbolic operation in our computational model. (p. 217, 2nd ed.)
It's true that by "object" they mean something more general than OO. But that doesn't change the basic design approach.
It's also true that they are careful to teach more primitive concepts first, so what they do is compatible with the OP's point. But there's a reason why the above passage on real-world representations appears immediately after they mention modularity.
- Square Roots by Newton's Method - Testing for Primality - Arithmetic Operations for Rational Numbers - Extended Excercise: Interval Arithmetic - A Picture Language - Symbolic Differentiation - Representing Sets - Huffman Encoding Trees - Symbolic Algebra
SICP teaches algorithms and data structures before that quote. And goes on to teach more about data structures without doing any of that awful OO modeling exercises you typically do in a typical Java Programming 101.
>But there's a reason why the above passage on real-world representations appears immediately after they mention modularity.
After that quote, you return to data structures. Okay, after lists, queues and tables, there is a chapter about simulating digital circuits, which is both a fairly abstract and a well defined thing. Well, we can disagree on how abstract a digital circuit is, but at least we can agree that its no fucking dog that is_a animal and has_a head and has_a leg array and methods like
bark(int howManyTimes) {
for (int i = 0; i < howManyTimes; i++)
println("woof");
}
Using OOP to model real world stuff wouldn't be so bad if it would happen in the context of real problems, like simulations where you can show how to use the state pattern to model behavior. What I want to see gone is that stuff like inheritance and composition is explained with examples taken from the physical world, completely detached from the programming practice. If you use File, Directory and FileSystemEntry as examples for a hierarchy, you can demonstrate it's usefulness with concrete examples. With Dog, Frog and Animal, not so much.When he needs inheritance he will know about it.
OOP is useful because it helps tackling some common non-functional concerns, mostly modularity and as a result extensibility, reusability and, to some extent, maintainability. These are architecture concerns. They're vital to take into account when you're writing any piece of non-trivially large software (and OOP is one, though not the only one, good approach to deal with them). They're entirely unimportant in small little programs, which includes nearly anything a beginning programmer makes when learning.
This also means that languages that religiously enforce OOP, like Java and C#, might not be the best starting points indeed.
Yet, there is an entire generation of programmers that learned OOP at school, and are hitting that hammer on everything, hoping for it to be a nail. That's all that's wrong with OOP: it does not solve all the problems in the world.
window.open();
Line aLine = new Line(...);
window.add( aLine );
I will never forget the day he came to me - after about 3 years programming in Java to ask, "Where are the methods for ints documented?" and I and we had to have a Birds and Bees conversation to the effect that not /everything/ was an object. He vanished for several days after that conversation to process this oddity. I began to wish we'd chosen Smalltalk rather than Java. (Actually I still wish that as a matter of general principle.)No. I believe that we simply approach the problem wrongly. I think that objects are (and were deliberately intended to be) closer to the way we naturally think about the world. It's the baroque mental twists and turns we have to learn to make on procedural programming that are the problem. If you don't learn that odd way of thinking first, you're just fine with learning OO first - streets ahead, in fact.
What? For me the "modern OO" is as baroque as it gets.
First of all, and your example reminded me of that, because it hides what's happening. What's a window and a line? How am I supposed to know what's happening, if one has the .add method? Docs, sure, but it's non intuitive. Granted, today there's so much complexity you can't handle it all in your head.
But especially because procedural is how the computer works. Assembly. A list of instructions that are read in sequence. That I can understand. Now, OO makes this flow very convoluted, and the more "OO" someone programs, the shorter the procedures are, rather relying on inheritance, types to make computations, which of course, are good in one sense, but break the train of thought.
For instance, in OCaml, the function "counter" defined as: "let counter = let x = ref 0 in fun _ -> x := !x + 1; !x ;;" is an object with one method that increments and returns the value of a counter.
In my opinion, OOP is intrinsically more complicated that FP because you can't really avoid complex patterns even to do simple things.
An example that comes to mind is the "Visitor" pattern. To me, it requires more mental gymnastic than what is found in most functional programs. It amounts to mixing higher order and functions with state which is something you try to avoid in FP.
But I admit that I'm not well-versed in OOP. I've always found it complicated. Not Objects per se, but design of big programs using classes with complex relations, UML, design patterns and so on...
OOP really is just about object thinking, just like FP really is about thinking in terms of functions. All the other baggage in either paradigm is quite optional.
openwindow(window);
Line aLine = make_line();
addwindowtoline(aLine);
A small pet peeve of mine is people insisting that encapsulation and abstraction are exclusive to object orientation even though abstract data types are also a core concept in prodecural or functional programming.To make your example "trully OO" you would need to be using dynamic dispatching and polymorphism in those method calls. But are we really going to need multiple Window and Line classes and even if we do, is this really going to be the best way to architecture our code?
Window_open(window);
Line aLine;
Line_make(aLine);
Window_add(window, aLine); open(window);
add(window, make_line(...));This code snippet posted does not implement a new class. In a language like Python, you can code for years relying on objects without ever implementing a single class of your own. As a programmer, deciding when to switch from writing procedures and functions to writing classes and methods can be a tricky design call. The OP's argument is focused on this issue, not the simple matter of using OO APIs or frameworks.
Additionally, it makes students have fun programming, since it gives them some graphical power and making a computer draw things is much more exciting than making a computer print things on a terminal (especially for newcomers). Plus, even though it's very simple, you can write some very interesting stuff with Turtle[2].
[1]: http://docs.python.org/3.0/library/turtle.html
[2]: https://github.com/FranciscoMSM/IPRP/blob/master/4linha.py
Kids seem to prefer writing programs that "do" something, where their definition of "do" might be different than ours.
One person we started out with some Java since he was taking a Java class, and then I quickly found that he was very confused with Java. I shifted gears and had him start Python from scratch. His understanding skyrocketed, since he was getting to understand how control structures worked, defining functions, and using them.
There is something to be said about the importance of understanding a compiled language though. While it depends on the individual, I think most people would benefit from learning with a language like Python first since some of the most basic concepts of programming become more accessible.
Interesting thinking, never thought of it this way.
I think the people that have problems to grasp OO, are the hacker type of guys that code away without much thinking about what they are trying to accomplish.
OOP requires putting the keyboard aside and think about the architecture of what you are trying to accomplish.
To put this in perspective, the PLT group of languages starts off eschewing not only OOP but mutation as well. Even more relevant, these are not options in the introductory languages.
This means the practice of not using OOP (or mutation) does not rely on the beginner practicing a type of discipline that even seasoned professionals might struggle for, i.e. a large class of kludgey habits cannot be developed when the PLT languages are used. A side effect of the PLT sequence is that it suggests the idea that switching languages isn't a big deal - rather useful since different languages are suited to different problems.
At the end of the day, every abstraction leaks so the student has to have a good working mental model of computing. And your first programming langauge does not dictate your career. You're expected to keep yourself up to date and have continue honing your craft.
I have taught three people how to program (not a large sample size I know...) each one I have chosen a different approach. It honestly depends more on the individual than the language.
Individual 1: They were interested in history, video games, politics and desired to learn programming. For him, he seemed more interested in the history of programming, so I chose to help him learn BASIC as his first language simply because of the history associated with it (I could have chose FORTRAN or COBOL). He enjoyed it more because he could connect, he thrived off imagining the other early programmers writing in BASIC. Further, he felt as if he was building and learning in a similar fashion to most programmers and that made him want to learn more languages advancing in a similar fashion.
Individual 2: For her, NetLogo was the simple choice. She understood nothing of programming and honestly did not want to at the time, but knew she needed programming knowledge to help her obtain a job in biology in the future. NetLogo offered a way to both teach her the basics of programming (objects, loops, etc.) while being pretty fun, we made an eco-system. This actually excited her about programming to the extent that she is now learning python and joined ACM.
Individual 3: For him Ruby was probably the best introduction to programming, he was interested in learning how to develop website applications. Due to the fact he had no programming experience and was interested in web applications it seemed clear (to me) that Ruby was going to be the easiest way for him to learn to program. He loved it, and has since moved on to learning C, since it turns out he loved the control programming offers him.
The point, is that each programming language has a community/documentation and specific uses, it depends on personality and what ones goals are when determining what language to learn first. Since I have always been interested in both improving myself and others I have read a few books on the subject, my favorite is The Talent Code [1]. The book explains that each person has a different personality/experiences and in turn "learn best" in different ways, which is essentially what I employed here (without explicitly meaning to).
I agree with the author that Python (in many cases) is the best language to have a want-to-be programmer learn. It offers simplicity, logic, standard libraries, community, and tutorials in abundance, however it may not excite a person or have applications in what a persons interests are (which can reduce enthusiasm and in turn learning).
thanks. I feel oooold :)
1. Very basic, sequential computation. Add two numbers. 2. Conditional computation. Find the absolute value of two numbers. 3. Computations over homogenous data: Arrays and loops. Find the average of the absolute value of a set of numbers. 4. Computations over inhomogenous data: Structures. Find the area of a set of rectangles. 5. Computations over "even more inhomogenous" data: Objects. Find the area of a set of shapes of different types, and find the centroid of them.
That's where you ask "how would you do this with what you've learned so far", and the learners should start to build different structures with a lot of similar aspects and duplication of code (I realize I didn't explicitly talk about the value of functions/procedures above, I guess it'd go somewhere after 3), and then you show them how much more straightforward it is to do with OOP. Now they know the value of OOP and what "doing it without" would entail, and you end up with a bunch of more knowledgeable programmers who won't needlessly create baroque class hierarchies and write more code than they need to, but will use those abstractions when they have value. There is a huge difference IMHO between e.g. being taught dogmatically "you MUST use functions because abstraction is good" like it is something you should take at face value, and being taught by being given a problem in which you do not know about functions at this point and thus write lots of duplicated code and then being shown the value of eliminating that duplication via functions.
I've seen way too many learners create half a dozen classes with a ton of methods in them when given a problem that could be solved in a single function of a few lines, then have trouble figuring out what to put in the method bodies... it's completely backwards! No matter how much "architecture" you manage to create with deeply nested objects, in the end all the functionality of your code is essentially based on a series of statements executed in sequence, much like they would be in a purely procedural language. The fact that many otherwise highly-regarded educational institutions are churning out programmers who can create dozens of classes with dozens of methods in them, and then not know what goes in those methods, i.e. the actual "meat" of the computation, is something I find highly disturbing.
So what? That is kind of like saying that people need to understand how an airplane works in order to ship a package overnight. Sure there is value in understanding how a computer works, and that will probably be taught later in the curriculum; but teaching students how to think abstractly is far more valuable.
"the sequential execution concept is something that is intuitive"
So is the concept of reducing one expression to another; we teach algebra as exactly that long before we teach anyone how to program.
So what? That is kind of like saying that people need to understand how an airplane works in order to ship a package overnight.
But people DO need to understand a bit about the process of shipping a package, if they want to estimate when it will arrive, or if they want to be able to read the online tracker, or even know that they must bring the package to a particular place before fedex will ship it. Or if the package does not arrive, what would you have to do to find it?
Generally speaking, understanding how a computer works is important to programming because the essence of programming is mapping a real problem onto the available computational hardware.
Not that I want to argue against learning about expression reduction, I would agree that should be done sooner than later, but it's not an argument against learning sequential algorithms.
Maybe. But I've never met those students at real life (ok, I have a very small sampling set). The imperative paradigm is easier to learn.
func(if(x == 5) foo else bar)
In other words, treating an "if...else" statement as something that can be reduced to a value. This frustrated the students because it did not compile. We had to train them to think in terms of sequential execution, despite the fact that they did not become any better at programming in the process (worse, they had to un-learn a perfectly valid way to think about programming -- only to have to re-learn it later if they took the programming languages course).Thinking in abstract terms is all well and good until you actually have to run code in a real environment to solve a real problem with the tools you have available.
return (x == 5) ? foo : bar;
def foowiz(x, threshold = 5)
if x == threshold
foo
else
bar
end
end
And then a call to func(foowiz(x)) would work - even in an OOP language like Ruby.What did the students need to unlearn?
In the intro CS class I took an age ago, we had to implement OOP as a final assignment in Dr. Scheme, which is a derivative of LISP that is most assuredly a functional language rather than OOP. That did not mean I had to unlearn how lambdas and tail recursion worked.
Then I use those languages myself. ;-)
What i learnt was: Forget about all this dog inherits from Mammal bullshit(it doesn't work that way,its just wrong!). OOP is good for a few things. 1. Sharing state between a collection of functions(call them methods if you want to use two names for the same thing). 2. Code reuse (inheritance)
Im rather amused that test driven development forces you to simplify OOP as much as is humanly possible,thereby going against the advantages of a shared state,which is one of the reasons you would use an object in the first place.
if __name__ == "__main__":
Python is there for when you move beyond beginner and you start to need libraries.For everyone else, there's javascript: open up your browser console and you have a full IDE with breakpoints and inspection, and an interactive REPL. Object orientation is there if you want it, but what 5-year-old does? Functional paradigms are there when you're ready for them (map and foreach on arrays, for example). A smooth learning curve that gives you power when you reach for it, and a fast, flexible GUI environment. Free source code to study comes with every web site.
Best of all, its flexibility fosters many differing programming styles: consider jQuery and Angular. Very few programming ecosystems could give rise to such unique styles. I wish my first programming language was javascript, instead of C64 BASIC, with its 38911 basic bytes free.
If you honestly think
if __name__ == "__main__":
is more complicated than js variable scoping rules and closures, I'm intrigued by your mind.You'd just have statements in global scope and no main() function.
<script type="text/javascript">
$(document).ready();
</script>
int main() {
return 0; // may or may not be needed depending on version of C/C++
}
class Main {
public static void main(String args) {
}
}
Perhaps you should try Haskell, since you seem to care so much about how to write main... main = print "blah" $x = new Object;
$x->bar($foo);
vs. $x = object_create();
object_bar($x, $foo):
See, for example, the "procedural" version of MySQLi.It's probably easier to talk about it if you've done a little imperative programming and can grasp OOP as a solution to actual problems, rather than just some abstract nonsense.
Yes, I think this is a real distraction. This Cat/Dog/Animal trope leads to the view that you should use classes to model your domain objects. In some cases, you should. But this can cause as many problems as it solves, and should not (IMO) be the default approach.
In getting away from this view, I've found it helpful to think of most programming as designing DSL's. To that end, you can use OO to build the infrastructure necessary to support this. But I worked in OO languages for many years before grokking that.
For all its (many) flaws, PHP was (and is) an extraordinarily successful project relative to the vast majority of programming projects attempted. This is in fact a confirmation of the original post's thesis. The theory is that teaching OOP to beginners bogs them down worrying about the architecture of their code before they've had any experience solving problems at the scale where the architecture matters. It's easy for a beginning programmer to waste hours on pointless refactoring that doesn't actually improve their program in any meaningful way, simply in order to appeal to some professor, teacher, or other religious figure's concept of "good code". PHP, on the other hand, said "screw you religous types, we're just going to design a product people will use."
The reason that OOP should not be taught to beginners is not because it's a difficult concept. The reason OOP should not be taught to beginners is that it is an advanced concept. There are many other important fundamentals that can come first.
That's not my premise. I just point to PHP (and its community) as it has some examples of it.
>The reason OOP should not be taught to beginners is that it is an advanced concept.
I've never quite got this. Spend any time writing code that's not using classes and you end up reinventing OOP anyway.
I've never quite got this. Spend any time writing code that's not using classes and you end up reinventing OOP anyway.
I think that letting beginners rediscover this for themselves and then exposing language constructs that make this easier probably allows a smoother path to enlightenment.
It's often not obvious to beginners why copy-pasting code a bunch of times is bad compared to using loops and functions for example. It certainly wasn't for me, until I tried to build a program that was not highly trivial.
class Hello {
public static void main(String args[]) {
System.out.println("Hello, world!");
}
}
Versus: (print "Hello, world!")
Yes, this is a deliberately extreme example, but the point is that object-oriented code does not make it easier to structure CS101-level programs. All it does is introduce a bunch of keywords that students have trouble understanding until they are sitting in a much higher-level course.Here's a sample programming problem a beginner might encounter: CIDR data is available as a "127.0.0.1/8" style string and you need to access the IP and Netmask separately. In python, the simplest, most straightforward path to a solution that still results in encapsulated, re-usable code is to write a standalone function that accepts a string and returns a tuple. You could also create a CIDR class with a "to_tuple" method, or that stores the values in 'ip' and 'netmask' attributes, but this is simply extra scaffolding and without knowing more about the larger context of the program it's impossible to know whether such a design will be beneficial.
The problem with beginners is that they'll see the straightforward solution, but because they're not just trying to solve a problem but also demonstrate "Object Oriented Programming" skills to their teacher, they're going to worry about doing a whole bunch of extra work that may not be appropriate. I believe this is wasted energy that could be spent gaining real experience solving problems and learning about how computers and software work.
I find that it only really makes sense from a code structure point of view once you have got to programs that are significantly bigger than the average beginner is likely to be writing. Even as somebody who understands OO I don't tend to use it for programs that are less than at least a few thousand LOC because I find that it just gets in the way.
"Spend any time" implies substantially more experience than what most beginners possess, hence the term "advanced." "Intermediate" might be a better term in an absolute sense. OO design is something to learn after you understand the basic tools and techniques.
Also, a lot can be accomplished without OOP (for most values of OOP) and even if you're writing de facto OO code, it's not always clear whether using explicit OO language features is a net win or not.
Only if you start with C. I find that if provided with reasonable alternatives (like function composition) or consistently restricted problem domains (vector manipulations) you very well might never create anything resembling objects.
If I call a function with some arguments, that function is composed of smaller functions which can only ever operate on the arguments I passed the parent function. As I follow the function down to primitives, each piece of code is operating on less data.
If I call a method, that method can operate on its arguments, properties of the object, and properties of the superclass. I often find myself ping-ponging around the code base as a class' method relies on a method of an abstract superclass which itself relies on methods of the original class, each step introducing more properties into the calculation, many of which are objects themselves!
When you teach it's not about you or what you think would have been best for you, it's about understanding other people's needs.
If you think bring up OOP should wait for some time in the future then wait to bring up OOP. If you think that OOP should be the basis from which programming is taught, then start with OOP. Just be a good teacher and keep them motivated. The whole goal is to get them past it all anyway.
Also, programming IS messy. Extremely messy, and teaching people as though it's gonna be all greased tracks the whole way isn't doing them any service.
- Nothing to install, you most likely already have a full blown development environment installed (e.g. Chrome or Firefox).
- The language is straightforward, functions everywhere, easy control structures, very little to be distracted by.
- The reward loop is very high for beginners: load a page, tell them to write a Javascript one-liner and see their face light up when the HTML page gets instantly modified
Unfortunately, the two other points you mention probably outweigh the actual language design.
"The shift from procedural to OO brings with it a shift from thinking about problems and solutions to thinking about architecture"
Couldn't agree more... I have been doing OOP for a while in various prog. languages, and I had come to similar conclusions...It doesn't really help you focus on core ways to solve problems: focusing on algorithms.
Another read I would recommend is from the author of C++/STL:
http://www.stlport.org/resources/StepanovUSA.html
See the answer to the question:
"I think STL and Generic Programming mark a definite departure from the common C++ programming style, which I find is almost completely derived from SmallTalk. Do you agree? "
Note: not trying to endorse C++ templates here either...have my own gripes here but it did bring the attention back on algorithms and data structures.
The easiest example I can think of is: using Ruby without getting into how to define/redefine classes or methods. Just stick to instantiating simple data types and invoking their methods:
my_string = "Hello world"
puts my_string.upcase
# => "HELLO WORLD"
puts my_string
# => "Hello world"
Among the first roadblocks beginners seem to have is grokking that `my_string` isn't modified...but isn't that an early point of confusion for functional programming too, to some degree?... and teach declarative programming (Functional or not) to the pupils interested in the background of computer science and targeting some innovation/invention trajectory in their "career".
In other words (and very exaggerating):
imperative programming : translating more easily "real world" into code
declarative programming : translating more easily "platonic world" into code
ps: no, i don't believe this to be absolute truth, yes it is quite a hyperbole ;)
Ok, but only after you teach them imperative programming. That's because it's also easier for them to learn the imperative paradigm, and you want to minimize the amount of "pollution" they have to learn alongside with "what is a computer and what it does".
That's also why avoiding OOP at this stage is a great advice. Architecture is "pollution" too.
agent: computer
imperative commands: instructions you give it
... but later on once the understanding of the machine/tool is established i see how much more powerful the declarative resp. transformational "paradigm" gets for "computers" applied to the "platonic world"
I believe that the transition towards the declarative paradigm and understanding of it is essential to grasp the idea of how "computers" and computation differs from other physical machines mankind invented.
I like this for new programmers as they are often trying to solve a problem or make a game. My step son made a nice chunk of change by building a website for someone -- the last thing I would have tried to teach him was oop!
OTOH - there is a point where learning OOP and forms of modularization are a good idea to expand one's knowledge of the art - just like everyone should read tcp/ip illustrated and hackers and painters (#pandering on the later)
I would have thought this was self-evident.
I think the biggest thing that stuck-out to me as being utterly wrong with teaching Java and OOP is that you're forced to talk about references/pointers extremely early on, when most of the students don't have enough grasp on the core language to understand them conceptually (And really, who understands a reference/pointer the first time they heard about it? let alone 2 months after starting programming).
I honestly think starting off with C would have been better, and I don't view C as a good beginners language. But C itself has a pretty simple core language, and writing small procedural programs in it isn't very hard to conceptualize, so it requires a minimal amount of hand-waving to get simple programs running. (Hello 'public static void main(String args[])') The biggest hurdle would probably be string handling, as it's an array of char's. If you're not concerned with buffer overflows though, simple stack-allocated arrays work fine for tons of simple introductory programs, and <string.h> provides enough to get by without messing with any pointers.
Bad choice of analogues aside, I (somewhat) agree with you. Python is a very beginner-friendly language.
Whether it's appropriate to start with an OO language I guess is another question, but I think done the right way, starting with OO isn't as hard as people think.
But, but, someone somewhere in galaxy far away already made lib so you can use it in C with only few lines of code.
There is a place for the low-level details: computer architecture, compilers, and operating systems. Students should take these courses to be well-rounded and to see how theory is translated into practice, but elsewhere things should be done at a high level.
I'd disagree somewhat. Data structures, from a CS perspective, often involve learning about low-level implementation details (why is accessing a hash O(1)? Why do array elements have to be the same size? What's the difference between representing a graph as a linked list versus a 2-dimensional matrix?). It's similar with algorithms.
Even though you CAN use completely abstract computational models to learn algorithms, many of the most important algorithms are designed around tradeoffs between real-world resources and being able to map abstract algorithms to a low-level language like C is a very useful skill to those who have it.
Back in high school in the 70's our first language was a variant machine language CECIL - which we had to do by sending of coding sheets to a bureau.
First, many people need a simple introduction to the entire concept of programming-- codifying precise instructions to a computer to get specific results.
Second, python is a great tool for exploration of other low-level systems, eg the socket library, the posix library, the os library.
Bad code is bad code. On the scale of encouraging good practices and discouraging bad ones, Python does pretty well given the very low overhead to get started-- something Java does not have.
I asked my daughter's AP comp sci teacher: Why Java? A: because that's what the test uses.
The Anti-OOP propaganda gets stronger.
Didn't Alan Kay of Smalltalk fame work with and for children?
(I checked the comments on the last three times sunmissions of his spellchecker article, but no one seemed to raise this point...)
Don't distract new programmers with computer science. ;-)
People learn stuff well when it is rooted in reality. Make them make few simple programs that could be done with excel.
And right now with these insanely rich libraries that are on the market you could spend half your career without knowing what O notation is and have no problems.
Another case study is Scala language, which is functional in the first place, and uses OO as a convenient way to structure the code, which, together with message passing were Alan Kay's original concepts.
OO should be a nice optional add on, not a forsed the only way to do it all as it is in case of Java or Ruby.