When I learned C and Unix, the instructor had us reimplement libs like string.h and stdio.h, command line tools like date, ls and cut and then build tools like make.
It made me a much better C programmer.
I think a nice approach would be to start off with a high-level, functional language like Haskell or Scheme, and then move down the ladder of abstraction in subsequent courses, culminating in a hardcore C (or maybe even assembly) course.
Overall, it's a pretty good system. The middle Java class was completely worthless and a big waste of time though; in hindsight, I should have skipped it. Also, while we learned a bunch of cool things in the SICP class, every single other class except for programming languages/compilers completely ignored it. Most retaught some of the same concepts, but poorly.
Also, apart from SICP, there aren't any undergraduate classes doing functional programming! What's up with that?
At least at my university, there's plenty of C and C++--even in places where it blatantly doesn't fit, like the other version of the compilers class. And far, far too much Python. And too little functional programming. Ah well, c'est la vie.
I would love a compilers course taught in ML (maybe OCaml?), and it's a possibility, but not before I graduate :(.
I had to discover and learn Haskell on my own :)
I also had to find Haskell on my own, but I think it worked out reasonably well in the end. The type theory/semantics class did use a bit of OCaml, but it took a while to get to it.
Also, I should add that--largely due to my own preferences--I've taken a disproportionate number of classes that do not really focus on programming at all. The aforementioned type theory/semantics course only had a token amount of programming, and three other CS classes had no programming at all. They were also some of the coolest and most fun. (After all, I can do programming on my well enough; theory is a bit harder.)
1.) http://yannesposito.com/Scratch/en/blog/Haskell-the-Hard-Way...