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.
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'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.
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.
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.
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 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.
> 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)
C programming - luckily in my opinion - has a culture of very rigorous evaluation of programming techniques especially towards memory-unsafe programming, arguably the biggest weakness of C as a programming language.
This book uses gets() which is a function that can not be used safely. Any input that exceeds the buffer length will corrupt memory. The conventional wisdom in the C community is to NEVER use gets. I personally stopped reading in chapter 4 where this is done.
Unfortunately strictly avoiding memory issues is somewhat involved und thus often ignored in C beginner books. It is also hard to see unless you are pretty familiar with C's pitfalls. IMHO this is why this list of bad books is desperately needed.
Unfortunately, there are not a single but a multitude of cultures within the C programming community that compete with others, most of them claiming they are safer than others.
The aforementioned list for example has Jed Shaw's Learn C The Hard Way as having "[t]oo many factual problems and a presentation that gets you to do things wrongly before being shown how to do it correctly, and not even always then". This is an opinion held by the ##c channel, not necessarily every (competent) C programmer. LCTHW itself did a great service by introducing valgrind very early, and most criticisms [1] seem to be presentation issues that might be partly necessary for beginners and partly a matter of taste. (I personally think LCTHW was in particular unfairly attacked because of its merciless treatment of K&R. It's a shame that Jed Shaw gave up then.) To this date I don't have any good beginner-level C book to recommend, including K&R.
I see usages of fgets in Ch4, not bare gets (didn't check thoroughly)
/* Read a line of user input of maximum size 2048 */
fgets(input, 2048, stdin);
So that fgets does not read in too much data we also must also supply the size of the buffer 2048.
Maybe they updated? Also I'd understand not trying to be 100% safe in the code here to make the code easier to read, unlike in a production version of the tool.
At least some of the things on that list have long been bugbears and known as sources for either incorrect/erroneous explanations and code (most of Schildt's output for instance). So it may be an attempt at gatekeeping but on the face of it seems a reasonable one.
I have little C experience. I've always wanted to learn it properly to broaden my horizons, so I really respect the opinions of the gatekeepers: C is a small footprint language with plenty of footguns.
It goes beyond matters of style and straight to things like memory leaks that can cause security issues.
My first reaction to those reviews was that they read pretty disrespectfully. Writing a book is extremely challenging and some of the feedback is not appropriate for a page that's associated with a standards body.
Then I wondered, "If these are the resources to avoid, where do these people recommend I find the good information?"
Alas, those links are mostly broken.
As a long time programmer recently learning C, I've been surprised by how prickly and unhelpful C culture tends to be.
The concensus seems to be that no published information is good enough, and none of the critics are willing to publish the good information.
It would be nice if they actually bothered to explain. I'm inclined to trust Build Your Own Lisp over blah blah dot info if the latter is going to engage in nonspecific mudslinging.
It teaches bad C, and more importantly bad Lisp implementations.
MPC for a lisp? No macros? No GC? No readline but editline, but not on Windows? fgets? A lisp is fine with a readchar() and putchar() alone. The rest should be done in a safe and more expressive language, lisp. Compare to a good and small Lisp instead. It need not to be "Lisp in Small Pieces", rather your typical lisp in 10 days or in 1K project.
I found this book, it's approach and tone to be really engaging, it answered a bunch of questions I didn't know I had and allowed me to understand and engage with my work as a web dev on another level. Parser Combinators for example are fascinating. I find Daniel Holden's work similar to many ways to Brian Lonsdorf's... Honestly surprised there are haters for this type of thing!
I'm currently re-learning C, and I've been going through one of the books that they recommend (C Programming - A Modern Approach).
Whilst "A Modern Approach" is a great book, it's hard for me to keep focused as I'm not really implementing anything other than the exercises and projects (for which there are too many IMO, and I struggle to get them all done).
I've been working through Build Your Own Lisp in parallel, and it's great fun. I get to work on an actual project, and any holes I find in my knowledge can usually be filled in via A Modern Approach.
"Effective C" is usually what I recommend people nowadays on Libera #c, which is where that wiki originates from.
The books were triaged based on the feedbacks we heard, the kind of questions that are asked on the channel, or how confused those readers are after reading through them.
Building a simple Lisp interpreter which can be then embedded in your project is the single best thing that you can do. Configuration? Solved. Serialization? Solved. Separating the application logic from the gory details of its implementation? Done.
That's one of the things that initially drew me to Lisp. If I had to embed a scripting language in something else, it may well be that the simplest thing is to write and embed a simple Lisp interpreter.
"Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp." -- Philip Greenspun
Be warned; Buildyourownlisp is a great book for novice C programmers looking to build more complicated projects but it's a very bad introduction to Lisp.
Most of what makes Lisp great you won't find here.
MAL and this book serve different purposes, this book's purpose is learning C by writing a Lisp interpreter, whereas MAL is more focused on writing a Lisp interpreter and is language agnostic
I worked through this book and I think is a fine way to get your feet wet with C.
When I learn a new language, I like learning something else in the way, because language learning tends to be samey once you have gone through a couple.
This book was right up my alley, cause you get a sense of language design and the very basics of C.
After reading the book I managed to write some cli programs in C, modifying someone else's code.
The main caveat about the book: It Is an introduction in both it's topics. I know the bare minimum of C and I could not design a language. Yes, the lisp you build... Well I would not use for anything.
But is well written and it has exercises, and I believe exercises is how you learn.
I started going through the book, but from the start I deviated to use Rust and Pest PEG grammar to make the parser and the logic. Saved me many times of debugging, but also showed, that you do not have to stick to the library or parser that the book uses.
I’m a python programmer and my dream would be to learn C and an extension language like Guile or Ecl. Then I could write the performant bits in C and extend it with Lisp. I’m actually pretty proficient in Racket so I’m doing pretty well on the lisp side, but I’ve found the books for learning C lacking. I absolutely had “C a modern approach” and “Modern C”
The criticism may be justified but jeez the person who wrote that comes off like a HUGE asshole. Reminds me of people I've worked with in the past who think they know everything and have an arrogance so thick you could cut it with a knife.
Every Lisp programmer I've ever met is like this. I don't know why, but it's probably Erik Naggum's fault. They used to be called "Lisp weenies" and now would be called "abusers".
(The Clojure and Racket people are supposedly nice.)
I've seen Erik Naggum's name mentioned in this context before, but years ago I looked through a bunch of his contribution on comp.lang.lisp and couldn't see anything justifying this reputation. His articles rather looked quite eloquent and thought-through. Could you provide an example?
I just re-read Xah's notes and all of that rings a bell, but off of comp.lang.lisp Erik could be incredibly kind in helping other programmers. I was working my way through an early edition of _A programmer's guide to Common Lisp_ by Deborah Tatar and got absolutely stumped on the chapter on macros. Erik worked with me over email until we actually found what appeared to be a typo in the example source code and was incredibly patient with my dumb mistakes.
I've accessed that wiki.c2 for various discussions about programming before. However, I get confused with the format, specially here where it seems like a thread full of comments (no mention of usernames) there. (I like the relative minimalism and simplicity of the discourse though)
"God help you if you are going to write your first interpreter in C of all things... Without manual intervention[,] C programs do pretty much no error detection... I hate C with a passion." -- Hayley Patton, Don't Build Your Own Lisp
Jesus just reading the first paragraph tells me everything I need to know about this person, what a whiny little bitch.
Lisp and C are my favorite languages and most of the people I know who like one tend to like the other. I was surprised by this guy who praises lisp but hates C with such a passion.
Some of the comments here don't like this book for various reasons. I'm curious - is there a (or two) recommended book which serves this purpose - learning C and building a lisp (= learning how programming languages work)?
From there, sure, learn C++, Java, Rust, Go, Node.js, whatever toolset matches your industry. Start with the fundamentals, though.