Hacker News new | ask | show | jobs
by desine 1825 days ago
I've had this in my bookmarks for awhile, intending to work through it. I think I can probably even do it solo without the guide, at this point. After years of programming, and learning (or at least playing with) many languages, I've come to a philosophical conclusion of sorts: C and Lisp should be the two languages all students start with, in my opinion. Probably SICP style Scheme education, and classic simple C89. C is the barest of the high level, the least abstracted, the most work. Students should build their own data structures, memory management, improved string handling, etc. I think this gives a really solid foundation of what other languages' features actually do, their benefits and trade offs. Then Lisp, to show what metaprogramming can do, and demonstrate the power that a language can have.

From there, sure, learn C++, Java, Rust, Go, Node.js, whatever toolset matches your industry. Start with the fundamentals, though.

8 comments

Terrible suggestion. We need to kill all these c'isms as fast as possible. Teach people to write nice data-structures, with good api's, and with generic and sensible types. All these C'isms like returning -1 for false and 'nil' children seriously sucks. For a systems class it might still be suitable, but you could design good courses around other languages like ada, zig, rust w/e.

If you want to teach them a lisp as a first language, at least use Racket, which actually have modules designed for education. I think it's less of a headache to use a language with a more modern syntax, like python or pyret, but Racket is excellent of course. You can introduce lisp later in a undergraduate compiler/interpreter class so you don't have to talk about parsing for a month.

Why would you need to talk about parsing for a month with Lisp?

Lisp famously has one of the simplest syntaxes possible that is still structured, and one of the simplest data structures to understand. There's almost nothing to it.

And why would you introduce Lisp in a compiler/interpreter course, to people who are presumably already quite advanced in their knowledge?

Lisp is best as a teaching aid to introduce common high-level language concepts, in a way where it's clear how they all actually work in a familiar syntax- because it's Lisp nearly all the way down. (No black boxes.)

Most other languages suffer from a "trust me" problem, where students are taught what complicated things do but have to take it as gospel because they can't read the implementation, so they end up with high level concepts but no idea what's really going on to make those work, and a vague belief that "underneath" is more complicated and mysterious than it has to be. (Black boxes abound.) Indeed some students struggle to understand high-level concepts until they can see the mechanism.

I think their point was that teaching Lisp in a compilers class is a good way to avoid having to discuss parsing in-depth.
I'm fairly conflicted about this. While I believe that learning C teaches bad habits - manual memory management tends to account for some absurd percentage (70-80%) of CVEs in modern programs - it's also unquestionably tethered to the history of software, and a majority of the immense foundation of tooling on a Linux system is authored in C.

There is a terrific benefit to learning on a platform where you can not only look up documentation within the system (man pages), but also "dig deep" into the code of any particular piece of the system you are interested in. So in learning C, you gain access to the body of knowledge contained in a Linux system, or contained in a BSD, and when you get curious about something like "These loops and conditionals are all fine and well, but how does a window actually get drawn to my screen?" you have everything at your fingertips to go and answer it.

Is that connection to the machine possible without learning C? You could use a toy operating system written in something else, but you miss out on the honesty that comes from teaching the same system that people are actually using (or at least able to use).

I think Go might not be an unreasonable alternative. It shares C's philosophical heritage, removes the need for manual memory management, and is capable of exposing a student to pointers, creating images, and HTTP communication all within the stdlib and the first chapter of the official book. The only downside, as I see it, is that one would need to additionally learn something like Rust to be able to apply the concepts to things like kernel drivers and close-to-the-metal programming.

Go sounds like the worse of both worlds from a teaching point of view: too high level if you want to understand what the machine is doing; but inheriting almost all of C's bad ideas that the GP was complaining about (except for manual memory management).
> learning C teaches bad habits - manual memory management tends to account for some absurd percentage (70-80%) of CVEs in modern programs

this is a garbled thought. The average person may have trouble with manual memory management, and C requires some manual memory management (the stack is automatic), but that's not C teaching bad habits.

You can learn to do manual memory management in C, because C teaches manual memory management.

that doesn't make manual memory management a good idea for the average programmer, but that's not C's fault.

experience with C teaches good manual memory management in the same way that working with sharp knives teaches good knife management. Do professional people get cut with sharp knives? yes. Are sharp knives a good idea? yes. All the time for everybody!? nope

The point is not to tech "C'isms" but to teach how computers and software work at low level. Even an introduction to an assembly language would be helpful. Higher level languages still perform these tasks but hide them from the user. Even if most people will end up using a high level language, having an understanding of what is going on and of how things work "under the hood" is invaluable.
I also have had this thought - start people with very simple (C89) C and Scheme. When they learn data structures have them use C and scheme and show additional examples in class in Python/Java/JavaScript. When they take an AI class, they build the fundamentals in Lisp and see examples/ecosystem in Python. When they take some form of "Programming Languages" they get introduced to SML and see some Java+ANTLR. When they take Software Design I and II, they work in Java.

From that foundation, they'd have the exposure they need to expand out.

I also enjoyed the path my University took. You started with Java. Data Structures was in C++, Java, and Python. Operating Systems was in C. AI was in Common Lisp (with some examples in Java and Python). Programming Languages was in any language you wanted, but examples were mostly in C and SML (SML/NJ). Computer System Design was in HDL, Assembly, and C. Software Design I and II used Java. Requirements Elicitation and Specification used Z. Concurrent/Distributed systems I and II used Promela, SPIN, TLA, and code was in Java.

PUC-Rio (birthplace of Lua) does an intro to programming with Scheme (kind of a watered down SICP-type course) and then data structures in C.
That was the progression when I took (the now well-known) CS50 at Harvard. Learn C first, then Lisp, then write a Lisp compiler in C. That was a lot of years ago, so one could argue that is an outdated approach now, but I think it still provides a strong foundation of CS fundamentals.
fundamentals never become outdated
Universities should not fear teaching Lisp and Smalltalk in the first year.
Let me refine this slightly...

Universities should not have become fearful of teaching Lisp and Smalltalk in the first year, once Java gained popularity.

As a student, I disagree on C.

C is fun for playing with pointers and doing raw memory things, but it becomes overwhelmingly painful and frustrating so quickly. And for the wrong reasons. Once you have to touch macros, or want to reuse code, it is just … old. Not hard, not minimalistic - just old. C can be fun the way bash can be fun, or baking without a recipe. It is fun to overcome an arcane mess, for the challenge. But you really learn little, or the wrong thing.

And C is not easy to look up online either. It took me so many hours figuring out some things are just not possible in C (without making it a new language via macro madness). Things that should be possible. Like „generics“. Kinda possible, but never satisfying. You constantly have to endure verbosity over abstraction. Not because that’s how computers work, but because how people did it 30 years ago, or because some industrial microcontroller needs it this way.

IMO whatever C can teach you, assembly can do better. Without tricking you into believing it’s a practical choice for most anything.

I think Rust is visually offputting for new students and it’s not really a good choice, but at least you learn something important, when things get hard. In C you are either trained to view programming as the most repetitive, joyless activity ever, or to indulge in hacking your way around broken shit, disregarding cooperation (with your future self), universal elegance and safety.

If there is „one language everyone needs to learn“ it’s some accessible and fun, which let’s enjoy computers and not tell you limits at every opportunity. There is no way to avoid C entirely, if you enjoy programming anyway; at some point you have to interface or tweak C, most likely.

> IMO whatever C can teach you, assembly can do better.

Other than, oh, things like getting the same code to produce exactly the same result on two different machines, where the sizes of the types being used by the code are different.

I already addressed this. IMO the portability mantra is something I disagree over „C for everyone“. It’s an annoying obstacle, but not exactly fundamental or hard to learn on the job.
I would guess that it's hard to learn that on the job, if the job consists of assembly language programming.
Hmm, unless I am mistaken, does not using portable types stdint.h u?int(8|16|32)_t fix this?
Using the same int<whatever>_t on all the targets may fix it in terms of correct behavior, but doesn't meet the definition of using different types on the different machines in the same overall logic, while getting the same result.
I mean, you're right that this is a problem and it gives people the ability to shoot themselvesin the foot but bytes and byte order is apart of a C programmers framework for better or worse. I want to argue it gives you incredible control of the data at the lowest level and that's the tradeoff. If you write non portable code, it seems like you have to go out of your way not using standard portability types. If you define your own struct types I can see where this goes awray, but I think " __attribute__((__packed__))" eliminates alignment spaces for that case.

BUT, if you are coming from a networking/file perspective I can emphathize with you. Byte ordering is a pain for data serialization, even that has chance to cause issues and is no doubt a source of bug and pain.

I mean, I can write code that has, say, this somewhere at the top:

  typedef unsigned int whatever_t;
  #define WHATEVER_MAX UINT_MAX
I can write the code such that I can edit whatever_t and WHATEVER_MAX to whatever values I want, and it still works, without changing any of the rest of the code.

This is not very highly abstract, but higher than assembly language.

If you want to teach this sort of hardware independence, asm is probably not the best teaching tool.

I never got too into C where I heavily used macros. I've seen some magic in C where you define function tables for objects and recreate object oriented programming but to be frank it's painful mimicry as well here. The best mixin I've seen is where you use function pointers to help de-duplicate code.

I would say C taught and learned correctly can teach you the memory model computers use and the concept of pointers. Other languages do other things better, like Java can teach you about memory management via garbage collection and references.

I learned Objective-C this year and I love it.

It is a fantastic language to use because the tradeoff is obviously evident in the language.

You can use Smalltalk for a day. Understand the history. Understand the compromise.

And Objective-C captures a beautiful moment in the trajectory to ever more high level abstraction.

Kind of, they went a bit overboard with @
I think most of the crazy @ features are in Apple's branch. There's still the GNU version floating around working with OpenStep that is much closer to the original Objective-C vision.
And largely incompatible with modern Objective-C libraries, so of questionable utility.
I wholeheartedly agree with you. Lisp is a perfect example of an underestimated programming language.

It really is astonishing that most programmers (whom I know) consider Lisp to be this weird language with too many brackets.

> It really is astonishing that most programmers (whom I know) consider Lisp to be this weird language with too many brackets.

Programmers (humans really) are really good at having knee-jerk reactions to things and then sticking with the same opinion for some reason. As engineers, you would think we'd be better at looking at things objectively, but somehow discussions/thinking around programming languages tend to be a very emotional thing for many, so they stick with their first impressions.

The real problem with lisp programs is each of them has its own DSL (the macros) so you have to learn one language per program. (I think that's the reason also why the library ecosystem is lacking)