I think Scheme comes the closest to being an ideal teaching language. It has all the same advantages adduced (great books, simplicity, power) in even greater measure. For instance, there's no 'funcall, 'apply, and sharp-quoting everywhere, and no powerful but baroque object and package systems deeply integrated into the language. Scheme macros are a bit complicated (especially implementation-wise) but can be sugared over (e.g. define-syntax-rule or CL-like syntax for basic macros) and at least they're "correct".
Scheme isn't perfect, of course, and I'd love to see a modern Scheme-like Lisp with a canonical implementation and borrowing freely from Haskell, Erlang, and friends while preserving its minimalist ethos (somehow neither Racket nor Clojure is this).
Of course, the readiness is all ... while there's a beauty in simplicity, can a novice without any higher mathematics really appreciate the style of programming one does in Scheme?
- Borrows modern functional programming concepts from Haskell (type classes -> protocols, immutability, etc)
- Robust concurrency constructs. While not directly copied from Erlang, designed with clear understanding-of and respect-for Erlang's successes and failures.
- Minimalist standard library
The only thing I can think of that isn't minimalist would be the reader-macros for vectors, maps, sets, lambdas, etc. However, it seems like those serve to reduce confusion & strike a decent balance between uniformity of syntax and clarify of meaning.
I'm not an expert in Scheme, Clojure, or teaching programming, but here's a few reasons why I don't think Clojure as it is TODAY would be the best language ever for learning programming (I don't really think Common Lisp is either, despite the fact that I loved Land of Lisp).
- Clojure has these horribly huge Java stack traces when you hit an error. Newbies are going to run into errors frequently, and they don't know Java. Even I find this kind of frustrating when using Clojure (and I know Java), if only because it's annoying to have to scroll back just to read the error. This could change! I'm sure it will. But it's the state of Clojure today.
- Modern function programming and concurrency stuff isn't really necessary for beginners. Sure, teach them purely functional programming to start out with, a la SICP, if that's your thing, but I think that Clojure's concurrency primitives (various reference types, immutable data structures) are a bit too advanced.
Scheme is just ever so slightly simpler and I think that matters. Just my 2 c. I love Scheme and Clojure and Lisp, I'm just not experienced with teaching programming to total beginners, so take all this with a handful of salt. I tend to be conservative when it comes to these kinds of discussions and would be happy with new programmers using Python, because it's so much better than Java.
> I think that Clojure's concurrency primitives (various reference types, immutable data structures) are a bit too advanced.
While newbies rarely need reference types (other than var, of course), I don't think there's anything particularly "advanced" about immutable data structures. Considering that most people learn math before they learn programming, immutable data might even be an easier concept to grasp for a complete newbie than imperative programming with mutable data is.
You might think immutability is advanced but that's most likely because you learned to program with mutable things initially.
As an aside, I agree that Clojure's stack traces are pretty bad, but there are tools that improve the mess quite a bit, eg. clj-stacktrace. The stacktraces might not be quite as intractable to newbies if you integrate that into the REPL.
I would argue that it's very useful to learn about data structures and the immutable/mutable distinction upfront (hashtables vs. balanced 2-way trees vs. Bagwell 32-way HAMT vs. "exotics" like skip trees.) I dunno if CL make these as easily avail as clojure
Re: Stacktraces, they have nicer formatting at least, but cutting out irrelevant parts of stack is tough. You can also set
Scheme may be an ideal "teaching" language because it's so small, but it's not an ideal learning language because you will quickly run into problems with libraries and implementations. The RnRS is also not as good a reference as the HyperSpec+CLtL.
Common Lisp code is much more portable across implementations so there are way more libraries and bindings to things like graphics and sound around (and easily available via quicklisp).
Racket OTOH is the best environment for learning to program in that I've come across, and I recommend it to people who ask me for advice about learning to program (despite being the author of that article). If Common Lisp had a similar environment for newbies it would be sweet, but not many people (myself included) care about Lisp IDEs.
The problem with Racket is that it has a huge problem with NIH as a project, and a lot of the libraries are opinionated about the way you use things like state.
I tried learning Scheme. I found that the syntax was easy, basic recursion was easy, solving simple math problems was easy, and even dealing with cons and cdr was pretty easy. What's difficult is learning functional programming.
Anyone can pick up Scheme syntax really fast. Where it gets difficult is when people start telling you to not use state, not change values of variables, and to stop using side-effects.
Most people have never done functional programming. How does one make the leap from imperative programming to functional programming?
Here's how I did it for Scala. I went to project Euler, and did the first problem. Then I looked at other people's answers in the same language. I discovered they were doing these tiny little 1 line solutions using functional programming techniques, where I did much larger solutions. I started studying every aspect of how these solutions worked. Along the way I learned about fold, filter, map, and so on. Then I re-implemented my solution using functional idioms. Then I moved on the the next project Euler problem. It didn't take long until I had a pretty firm grasp of it. I did this in Scala; perhaps the same approach would work for you in your language of choice.
This feels like something that someone completely removed from people who don't know how to program would say. I can't imagine the horrors I would've gone through if I couldn't fall back on the fact that my first language (C++) basically looked like the mathematical notation I was used to (infix binary operands, prefix functions). Until the standard is to write math in prefix (I'm definitely not condoning this), Lisp is not the right language to start with.
This remains the biggest problem with places like HN. When everyone has a similar background, they can agree on conclusions that the rest of the world would find ludicrous.
I think your horrors would have been abbreviated with less to fall back on.
I TAed intro CS in Java for a couple years and the vast majority of fundamental misunderstandings students had were caused by preconceptions and expectations of how things should have worked based on exactly this kind of previous experience. I think I spent at least as much time helping people un-learn things as I helped them learn novel concepts.
In the short term Java and C are friendlier, but this friendliness allows a student to put off confrontation with the essence of programming in a way lisps do not.
As far as I know, the hacker news site is written in some lisp, with lots of lisp fans here.
I'm with you though. Lisp can be great for some problems but it's mostly not. The lisp guys hate hearing that and shout you down.
One measure of a language is how well it works to pass on a source base to a new team of programmers. I bet if someone were to go measure lisp against C they would find at least a 100x greater success rate with the passing on of C.
The lisp guys are profoundly focussed on how easy it is for them to write code. The real world is more concerned with how easy it is to maintain, review, enhance code.
Heh. As much as I agree with pretty much everything he says in that presentation the GP's point is pretty relevant here: Rich Hickey is far, far to the right on the programming bell curve. Things normal people will have real difficulty grasping are going to seem glaringly obvious to someone like that.
I learned to code C from a book years before I touched Lisp, and knowing operator precedence didn't help me understand one bit about what the code was actually doing or where the rules of the language came from. It wasn't until I found SICP that I could claim to understand programming.
The important thing about learning to program is having an accurate mental model of what goes on in your program. C, and especially C++, make that much harder than it actually is and the way Lisp presents it.
I definitely agree, with a caveat: I would say that (Common) Lisp is the best language to learn computer science. If I had only an hour to teach a young child some fundamental CS concepts, I would make sure to include the concept of recursion. Common Lisp makes this, as well as a few other CS 'building blocks', trivially easy.
(The reason I'm distinguishing between programming and CS here is that 'programming' is a broad term that encompasses a number of use cases. If someone wants to learn 'programming' to throw together a few simple webapps or do some text processing, Lisp will get the job done, but they probably won't care too much about learning these fundamentals).
That's not what I would teach first. I'd start with a simple assembly language, like 8080A. After that C, and then lisp. The problem with starting students on lisp is they have a hard time grasping what's going on under the hood.
I suppose it depends on which hood you want to look under. :)
I see CS as basically dual-grounded: everything is ultimately just syntactic sugar on machine code, and everything is ultimately just syntactic sugar on lambda calculus. Which reduction makes more sense depends on what you care about; the bare-metal view comes into play more when you're talking about low-level performance, while the mathematical view comes into play more when you're talking about algorithms and correctness. I guess it can get into some philosophical debate about which reduction is more "real"...
I'm not entirely sure which way is easier to start from. The argument you make here, that students have trouble going from mathematical abstraction to bare-metal implementation, is probably true; but I think students who first learn on asm and C also have trouble going the other way, from thinking about executing a series of instructions to thinking about enacting computations. That's one reason there's been some experimentation at places like MIT and UT-Austin with not starting with an imperative language, which they feel is very hard to get people off of once they've started with it.
Er...depends on why the students want to program, right? I studied assembly and I may be taking for granted the computer science foundations that it gave me, but it has almost no bearing in what I do in my current day programming. And I had learned C, C++ and Basic before my assembly classes.
Teaching students assembly as their very first language seems akin to teaching students Latin before they can learn French, German, Spanish, etc. Sure, it's helpful and...IF they get pass that first dry session.
An simpke, elegant 8-bit microprocessor is very simple to hold in one's brain. That said, the 8080 and its offspring would never be my choice. I'd suggest the 6502, the 6809 or the 32032 (which is not 8-bit, but very easy to understand)
There's a big difference between 'simple to hold in your brain' and 'simple to do something useful with'. For someone just starting out programming, the second is arguably much more important. The article mentions Land of Lisp presenting a simple web server in 15 pages of text. What would you be able to explain in 15 pages of text for assembly?
Agreed. As an electronics hobbyist, learning 6502 was almost stupidly easy (this was with past experience programming x86 assembly). Once I decided to actually implement a motherboard with an 8080 processor, I gave up after a few hours tearing my hair out over the datasheet. The timings are horrid, and the fact that memory locations intersect each other based on seemingly random equations...too complicated. I'm sure given enough time I could have figured it out, but all the external chips needed to deal with memory locations and IO would have been a nightmare.
I agree the 6502 would be a nice choice, but I'd be more tempted to go with a modern RISC architecture like MIPS or ARM- MIPS is extremely clean and simple, but it has the additional advantage of being something students are more likely run into in the wild someday.
I think you really are taking your own knowledge for granted. My sense in working with green programmers over the years is that the ones who never did assembly never have a feel for how everything fits together. They have real trouble understanding implementation details that matter to their day-to-day work.
That's not to say you couldn't learn lisp first and assembly later. But that seems like the hard way to me.
I agree. Speaking as someone about to graduate in CS, having started with C, a simple assembly language (LC-3), and later focusing on C++, I've found picking up new languages fairly easy with the stronger understanding of what's going on underneath. Obviously I'll never know what it's like learning higher level languages first, but I'm definitely happy with the path my school uses.
For example, learning manual memory management and instilling habits around needing to worry about this made learning Java and C# a breeze. I can't imagine moving from a language with garbage collection to C would be as easy or smooth.
I am in the unique position of actually having taught/tutored/TA'ed several intro courses in several different languages, which included Python, Java, and Scheme. I would actually say that I had an easier time teaching elementary concepts to people in Scheme, but that the transition from elementary concepts to general-purpose or practical programming was easier for people who started with Python. Overall, I'd argue that Scheme is a better language for introducing computer science, but that Python is a better language for introducing programming and development.
I think one of the biggest problems with advocating Python as a teaching language is that programmers don't understand what the hard parts of Python are. Python is great because it's sort of a lingua franca for people who are well-versed in conventional, Algol-based imperative programming—it boils it down to its essence and omits anything unnecessary—so to a person who was versed in Java/C/C++/Perl/PHP/&c previously, moving to Python feels like everything unnecessary has dropped away. Consequently, it feels easy, and you look at it and think, "Oh, wow, this would be no big deal to teach someone, even a non-programmer."
In reality, though, you'd be surprised at the concepts people struggle with. Assignment poses a huge problem to beginners, and I have spent hours and hours of my life trying to explain the concept to baffled undergrads. Not that Scheme concepts are necessarily easier (e.g. recursion—although loops are not necessarily easier to a beginner) but that Scheme makes a point to have far fewer of those concepts to begin with. Keep in mind that in an introductory class, every new feature, no matter how small, can mean another week of instruction, which is why an intro class in C++ could mean almost no projects at all, but an intro class in Scheme could very easily give way to writing object systems, programming languages, and adventure games, as my first computer science class—using Scheme—did.
I've taught introductory computer programming in Scheme and Pascal. (I think I just dated myself.) And I find myself concurring with this.
People who taught themselves to program often have a hard time understanding what novices find difficult. The one that gobsmacked me -- and I only ever ran into this when teaching Pascal -- was twofold: one, that the computer does things in the order they are in the program, and two, that the definition of a function or procedure does NOT execute the function or procedure. Something about Scheme made the distinction between "we are defining a function" and "we are executing a function" much easier to understand.
And I concur about the terseness of Python. I've been using Perl professionally for, um, 15 years now (and I just dated myself again). I've written a few toy programs in Python, enough to establish that it's a worthy programming language.
But the pedagogical problem is that there's a lot of depth under the surface of Python (Perl, PHP, Visual Basic) - a lot of things going on that the experienced programmer understands and that the novice programmer needs to. It's often easier to learn in languages that have less going on, because the programmer needs to do more explicitly.
It's not a brain dead verbose bondage experience like Java nor deep in the metal like assembler. It isn't historic and systems oriented like C or mathy and abstract like Scheme or LISP. It can take advantage of much of the semantics of those languages as you learn it if you need them.
The tools to write and run it are simple and some good debuggers are built into your browser. Everyone owns a few client interpreters with a great application environment.
I agree on a few points and also disagree on some.
HTML5 Canvas is also awesome - IMO it's the most convenient way for beginners to do computer graphics in any "real world" programming environment/language.
I do think the article make a good point, CL is hard to outgrow. You can learn almost any type of programming out there in CL. Python doesn't have that level of flexibility as far as programming paradigms as CL in my opinion.
Oh yeah? Well, by learning Ruby you get all that and you become a Rockstar. Kids love being rockstars. So there.
To the downvoters (and feel free to keep downvoting, if that rocks your boat): the "+1 for Python, because" comments were as empty as this one, and less warranted at that.
Ultimately where you start isn't as important as where you go. It's just a matter of picking a language, any language, some books, and shutting your ears down to everyone telling you that it doesn't get any better than that.
I adore Clojure (and Common Lisp to a lesser extent), but getting a new person excited about programming is far easier when they can do something useful really quickly. Python's massive collection of libraries makes that easy.
As I've said elsewhere, your choice of language must be driven by what libraries you want to use. I read Land of Lisp earlier this year and went through the Racket guide a few months ago. Currently I'm rewriting my blog using Seaside on Pharo Smalltalk. The one thing that keeps coming back to bite me in the ass is that there are few libraries available for any of these languages. When libraries are available, they're poorly documented.
Of course, this is becoming less of an issue in Common Lisp thanks to QuickLisp. There's a Racket book coming out next year, which might help with adoption and result in better libraries. Still, it will take years before any of these languages catch up to Python/Ruby/Java.
Edit: I thoroughly enjoyed learning and playing with all three languages. Smalltalk, in particular, felt very natural. It has the eerie property of seeming like an extension of your brain.
The first language I learned was clisp and it was the only one I used for the first two years.
It was awesome. It was a long learning curve, and I couldn't do almost anything for a really long time, but I got to a point where I knew everything about this tiny universe and had a solid foundation to branch out from. With that I went on to learning java, python, js, and c, and became fascinated at how much faster things became as I used more state and therefore got closer to the hardware.
For a while I was a little worried that I would only be able to use functional programming and would not be able to learn how to use state, but that was not the case.
I know you're being tongue-in-cheek with that, but I think there are three distinct use cases for learning to program.
For people who want to learn CS from a ground-up, theoretical foundation, Lisp is a nice choice. It is very easy to teach functional programming while simultaneously teaching the fundamentals of theoretical computer science, so you get the best of both.
For people who want a bit more 'traditional' approach, and also want to learn about computer science from a low-level perspective, C is a good choice. It teaches memory management and gives an insight into computer hardware & systems that higher-level scripting languages don't. (This latter part is where Java fails - how can you appreciate references and garbage collection until you've mucked around with pointers and malloc() in C?)
For people who just want to dabble in programming, or for people without a mathematical background, or for people who just want to prototype small projects quickly... then yes, scripting languages like Ruby (or Python) would be the 'best' way.
As you said, languages are like tools, but I think these three categories of tools cover the majority of use-cases nowadays for the beginning programmer.
I'd add a fourth use case (which is somewhat similar to case three). People who don't want to be "programmers", but just want to learn how to simplify and optimize the normal day to day tasks they do. In that case I'd recommend whatever scripting language is standard in whatever app they use. For example I know people who probably would never claim to be programmers and never had any desire to learn programming for programming's sake, but have still taught themselves to do pretty advanced stuff in VBA, AutoCAD Lisp or MEL (Maya's scripting language) because they saw how much easier it made their work.
RMS has an interesting anecdote about this in his speech on Emacs:
"[Bernie Greenberg] wrote a version of Emacs in Multics MacLisp, and he wrote his commands in MacLisp in a straightforward fashion. The editor itself was written entirely in Lisp. Multics Emacs proved to be a great success — programming new editing commands was so convenient that even the secretaries in his office started learning how to use it. They used a manual someone had written which showed how to extend Emacs, but didn't say it was a programming. So the secretaries, who believed they couldn't do programming, weren't scared off. They read the manual, discovered they could do useful things and they learned to program."
Well, I suppose I fall into this category: I'm a grad student in the humanities, with a fair bit of math background which I don't actively use in my work (apart from some simple statistics), and I mostly program to automate away things that take too much time otherwise. I was taught Pascal in college, but in a "monkey see monkey do" way (for instance, we did implement linked lists, but nobody really explained pointers to us, it was just "put a star there and it'll work"); later I picked up Python and also did a Django project. However, via a friend I discovered Common Lisp and I have never looked back; I just find it comes very naturally to me.
Still, I agree that something like Python is a very suitable language for somebody who just needs to simplify their lives. (At my old school they've switched from Pascal to C# now... why?)
There definitively better ways then others. Having seen full classes of people learning C++ as a their intro to programming I can say there definitively bad ways to teach programming (My wife was one such student, it really turn her off to the whole experience and I'm trying my best to get her to try it again with something better)
Ruby is a very good choice for sure. Between the two(CL or Ruby) I don't know which one I'd pick. I guess ruby has a little more practical aspects to it. Lisp has some advanced features that could be used for more advanced students.