Note to the unaware, both* this version and the classic (Scheme) version are Creative Commons-licensed and freely available online. (*edit: thanks to keithwinstein for enlightening me -- I wrongly thought it was just the one. The publisher's site doesn't advertise this at all!)
The in-line editor is a bit intrusive (fills up most of my screen, unlike say eloquentjavascript.net) but it's still pretty neat to have run-able examples. Kudos to the creators.
A ton of work seems to have gone into this -- they have a meta-circular evaluator in JavaScript (and it's not that much longer than the original Scheme)!
This isn't an ideal language for doing this stuff. The Scheme version is completely transparent about what you're doing; whereas this kind of language makes you work through a thick veil of abstractions.
(This line doesn't occur in the Scheme version, but the analog would be (quote (let ((size 2)) (* size 5))). Which would you rather debug?)
I know which one I'd rather debug, but which one can a new student get started with faster? They are unlikely to already have a scheme interpreter on their computer.
It won't take longer than 15 minutes to get racket installed, and that's being extremely conservative. If you can't spend 15 minutes getting setup done, why would you finish a dense book on the theory of computation?
When the Scheme version was written, all the programming languages were equally weird to incoming students. So Scheme was as good a starting point as any other.
Today, Scheme is still weird, but JavaScript is familiar. It's something the incoming students are at least likely to have heard of. So it makes more sense to ease them into the material with the familiar rather than drop them into something weird.
(Just to be clear, that was intended as a friendly poke, not a scathing rebuke. I do think lisps are better suited for SICP than JS is for the reasons others have mentioned, but I’m not trying to be a jerk about it.)
Making sense of those parentheses is genuinely hard for a newcomer, too. The Scheme version is more readable to you because you already know it - anyone who's actually working through this with a class level of effort instead of skimming pages is going to spend an equal amount of time learning how to read the syntax you quoted, and they'll be fine once they do.
You can see there's more negativity in this thread compared to the Python one. HN crowd is pretty hostile toward JavaScript.
Yet JavaScript is obviously a better choice than Python in terms of SICP Scheme substitute. For example:
- JavaScript is arguably a Scheme descendant, or at least have closer blood ties with Scheme than Python.
- The Lambda in Python is just awkward, and SICP is a book about lambdas.
- The Chapter 2: JavaScript use more anonymous object which makes it a better choice, although there are dictionaries in Python it's more favored to use Classes.
- Chapter 3, the Object-oriented Programming part: Use lexical closure to simulate object is just day to day JavaScript. So does dispatch and pattern matching messages.
- Chapter 3, the stream part: In JavaScript world the "functional" streams approach is also very popular.
- As for symbolic programming and such, they're both not particularly good. But that's a problem for all mainstream languages. At least JavaScript has programmable string template.
As you can see many SICP stuff are just day-to-day JavaScript, but not so much in idiomatic Python. Let alone JavaScript is more approachable in almost every platform, and much easier to make interactive tutorials.
But with that being said, in the JavaScript world, the tooling treat developers more like "users" rather than "engineers". For example:
- Many documentations are cohesive, well-thought, and well main-maintained.
- There are usually handy shortcuts for things like `npx` or `npm i <package name>`
- And many other improvements strive for developers' life-quality, such as call back-end and databases with strong typing, but without messy code generator at the same time.
Meanwhile, I have worked with Java and C# for quite a while, those documentations are so daunting, they feel like math textbooks from the Soviet Union. And companies like Microsoft never bother to fix their blur resolutions for SQL Server Management Studio and such. It's just like nobody cares, and they make working really feels like screwing codes on production lines.
> But with that being said, in the JavaScript world, the tooling treat developers more like "users" rather than "engineers". For example:
Tooling really is about becoming a user. It takes a dozen different file types and shoves them through at least a dozen different type-specific pieces of software. Understanding any one of those to any real degree would take weeks to months.
Just JS itself would require understanding transpilers, minifiers (an optimizing compiler), how NPM works, how these work with all the other tools, various module bundling styles, map files, feature differences for literally 10 versions of JS, browser JS limitations (eg, Chrome and FF break the JS spec for tail calls), JS API differences and polyfills, hot reloading techniques, and wrapping an entire server instance into all of this. I'm sure there are other parts I'm not thinking of.
I did the hand-rolling of all this stuff for several years before jumping on to create-react-app. I've spent many days reading the documentation only to have to read it all again when the next major version drops and everything changes in slightly incompatible ways.
I love learning, but on the whole, this adds very little value to most of the projects I've worked on (though some really did need hand-rolling for very niche requirements).
I rather like laving all the tooling to domain experts who know far more about it than I ever will. I'd rather be a "user" and leave all my engineering headspace for the problems at hand.
It's C# that created and popularized async/await, not JS (and it having an already open source implementation at the time, it was available to everyone for free).
Python is based on mainly on statements rather than expressions, and lambdas can’t have multiple statements, so writing lambdas is different from writing normal Python code.
In a very real sense, there is no idiomatic way to write complex lambdas in Python. What you would do instead is define a named function and pass it in as an argument. This is fine but definitely more complicated.
One major benefit here is how approachable this is. One could even simply open Dev Tools on their browser and use the console as a REPL. As someone who installed Scheme/Dr Scheme/Dr Racket long ago, it's nice to say, "you can get started right now". I know there'll be a lot of complaint about JS as a language choice but, it's pretty ubiquitous. And, much like Scheme, no one's saying, "you should use this for your next app that you have planned"
It's definitely a benefit but the trade-off is rough - the book itself is 'approachable' in the sense that it's written well in an accessible style. But it's dense in concepts and ideas and smacks you with them right off the bat. A lot of them are much more convoluted when expressed in JS, especially for the first time. Take a look at the sort of challenge the authors had to deal with:
I agree in contrast to the previous reply I made.
I still play with Shen, and I would love to see SICP in Shen, since Shen has ben implemented for many languages including JavaScript, but it's still a Lisp. I should try my hand at it if I really want to learn Shen.
I am also an APL/J fan and this footnote in SICP rings true:
[15] Richard Waters (1979) developed a program that automatically analyzes traditional Fortran programs, viewing them in terms of maps, filters, and accumulations. He found that fully 90 percent of the code in the Fortran Scientific Subroutine Package fits neatly into this paradigm. One of the reasons for the success of Lisp as a programming language is that lists provide a standard medium for expressing ordered collections so that they can be manipulated using higher-order operations. e programming language APL owes much of its power and appeal to a similar choice. In APL all data are represented as arrays, and there is a universal and convenient set of generic operators for all sorts of array operations.
I wanted to say Shen will never be relevant or useful without a change to it's licensing model, but looking at the website it seems like that exact thing has actually happened? It's hard to find concrete info so I'd love for someone to confirm, but IIRC Shen was encumbered by some sort of distasteful closed source model previously but it looks like that's no longer the case? Very excited to dive in if that's the case.
EDIT:
> In 2021 the SP kernel was returned to open source forming the basis for the S series of kernels on which this manual is based.
I actually subscribed for a while to SP, but I didn't like having to be connected to the internet to run it. I own two TBOS editions, and it scratches my Haskell, Lisp, Forth (because Klambda), and Prolog itch. I've toyed with the WaspLisp version, and I really want to dive into it. Deech's YT video years ago showing how you could program the front and back end in Shen, because there was a Ruby and JavaScript port really sold me. To me it is relevant, because I can use one language deployed on so many ports. Plus, I just really got into combinators after reading the beginning chapters of Stephen Wolfram's book on them. I don't live and die for open source-only projects. I think only the SP kernel, not all the libraries (grahics, etc.) were. I believe they are still in SP, but I may be wrong.
This is the wrong kind of approachable, because you have to learn a ton of random web things before you do anything outside the repl and before your program can even process user input and then people struggle with those two concepts that would be trivial to them if they could be introduced earlier.
Having seen what having javascript as a first language does to first year students I can't understand why this is considered a good idea.
I'm an uncle of 6 children, ages 7 and under. My guess is that if any of them have ever seen a VT100 terminal emulator or Windows console window, it was probably only when watching me or my brother-in-law. So traditional line-oriented programs, with plain text input and output, will be alien to kids of this generation.
I know Racket has an integrated GUI toolkit, but will this new generation be able to run it on their tablets?
One benefit of the great variety in front-end JavaScript frameworks, often criticized as churn, is that we're more likely to arrive at an abstraction that will be approachable to beginners. I look forward to watching how this generation learns to program.
Right, but if we're looking for the quickest path, "right click on this page, and select 'Inspect'" and boom. You're there. Certainly one could go to https://replit.com/ and choose from a host of "better" languages. My approach was trying to avoid someone having to install anything at all. As soon as you say, "ok, you'll need to install NodeJS, then use npm to install this package....", things get dicey. Someone who's serious about learning will fight through it, but someone who's just starting out may be put off.
First they replace Scheme with Python for their CS classes, and now SICP in JavaScript! Isn't this another portent from Nostradamus about the coming of the end of the world?
Like many answers in life, hell is not coded in Java or JavaScript, hell is coded by other people coding in Java or JavaScript.
Some things in life just turn into hell when you ask for everyone’s interpretation (politics, government, programming). It’s not fried chicken which most people cant fuck up even if they tried. Stick the damn chicken in the fryer, and whether you over-do it or under-do it, it’ll be fine with some ketchup.
It's not the worst thing to happen, but for the love of god, couldn't they have at least chosen an expression oriented language?! Even Ruby for god's sakes would've been better than JS!
If they were to choose another language, StandardML should have been the choice.
It was designed to be easy for students to implement with relatively easy parsing rules. It is fully functional, but allows mutations. It also has an actually sound type system.
I saw this the other day on Amazon while picking up the SICP instructor's edition. For the life of me, I can't figure out why they chose JavaScript to do this. And the new authors are professors.
Since the original SICP has a permissive license, I've actually thought about doing something similar, but with a more modern functional language.
> Yeah, imagine the craziness of making materials like this accessible to users of the most widely used language.
One of the reasons it was originally written for scheme is that scheme has a small core, simple syntax and can very easily be learned while reading SICP.
More importantly, the type of user that would get put off by using/learning scheme, is probably not going to make it through SICP. Learning just enough scheme is arguably the easiest part of going through SICP.
SCIP uses an amount of high-level functions that are frankly ridiculous outside of a very functional language. Hell, maybe even outside of a Lisp-1. That's not to say that Scheme is the best language (probably not in my top 5), but there are better languages than javascript for SCIP, and better programming methods than SCIP for javascript.
I guess that's the rub, I'm not sure any of them are. Certainly Javascript has stronger support for functional programming than Python. Maybe Ruby? Even Haskell would get pretty much unreadable extremely quickly, though pattern matching would make some parts way easier.
I'm not coming at this from a lisp fanboy perspective (though I am one), it's just kinda the reality of SICP's style of programming. Even PAIP translates extremely well to Python, but SICP encourages a layering of abstractions that I can't picture reading without parenthesis keeping order of operations strict.
The best solution I can think of is having a pre-packaged setup for getting started with Scheme, a la Dr. Racket.
One of the exercises in the first (or was that the second?) chapter is speeding up Fibonacci's iterative algorithm by viewing the state transition equations that derives F_n and F_n+1 from F_n-1 and F_n as a pair of linear transformations, therefore a matrix multiplication that can benfit from fast exponentiation (repeatedly squaring). Then in the chapters after that you code a symbolic differentiator and add pattern matching to the language.
SICP doesn't try to be accessible, it's explicitely an engineering text book made for engineers. That's even the reason some people have criticized it[1] and wrote another book[2] with the same themes and goals but ditching the excessive math focus. Anyone who can understand SICP's exercises can understand Scheme.
I got a chance to talk to Hal Abelson and two things I found surprising where: 1) he thought part of SICPs success was
due to the calibre of students MIT gets and 2) He though Javascript was a fairly good scheme approximation ( although with too much syntax).
>He though Javascript was a fairly good scheme approximation ( although with too much syntax).
that one isn't too surprising because "putting scheme in the browser" was quite literally what Eich set out to do
"As I’ve often said, and as others at Netscape can confirm, I was recruited to Netscape with the promise of “doing Scheme” in the browser. At least client engineering management including Tom Paquin, Michael Toy, and Rick Schell, along with some guy named Marc Andreessen, were convinced that Netscape should embed a programming language, in source form, in HTML. So it was hardly a case of me selling a “pointy-haired boss” — more the reverse.
Whether that language should be Scheme was an open question, but Scheme was the bait I went for in joining Netscape. Previously, at SGI, Nick Thompson had turned me on to SICP."
I hate this style of coding. I don't see how it's any better than turning everything into data and code in encapsulated class objects and passing them around
You shouldn't read SICP as a guide to practical programming. A lot of things in it would normally be bad style or contrived, but it's interesting to know that they are possible.
It's handy sometimes, but I think it starts looking worse the further from Scheme you travel. Even in Common Lisp it's probably too far, but with a Lisp-1 you get some really pretty high-level abstractions. Of course I find I don't miss them very much, but they sure are fun.
I took the CS1101S course taught by Martin Henz (who did the adaptation), where this JavaScript adaptation of SICP was the textbook. I enjoyed writing JavaScript and functional programming.
Here is an old screenshot of the interpreter that I was able to build:
SICP should be shelved into the museum of the great CS courses.
It served its purpose in its generation (the early batches like 1990s were already electrical engineers).Even Discrete-Math was just an experimental collection of concepts for them[1]. My mad respect and admiration to those professors at MIT.
We moved on. Assuming we have completed nand2tetris[https://www.nand2tetris.org/], we should instead be focussing on something like HtDP[2], or maybe like how they do at Brown using Pyret[3] or my favourite, how they teach programming at CMU - C with training wheels[5][6]
SICP as a second course, will then be fun to those passionate about mariad applications in the midst of innovation decades.
Javascript (and sometimes Python) won't give you the insight these courses were designed for. To learn programming? Absoutely NO.
No, thanks. It serves as a perfect blend of a serious bullet point in the introduction entries and niche comedy. It's part of the eternal culture around it.
Edit: to clarify, javascript is full of random warts that you have to know about in order to use it effectively, that's why I think a language with less such warts would be more suitable for someone that wants to try out and use the ideas book introduces with caring too much about the language.
The most accessible runtime on the planet. To each their own. I loathe JS, but I also believe english is a crap language. English happens to be the only language I can effectively use to describe my ideas to others. :-)
That's what I had assumed but wanted to double check since "accessible" is ambiguous. However, it's not all that convincing of an argument. Several languages are installed as easy as a browser is. And loading a local file, such as code included with a book, in the developer console quickly goes beyond accessible.
It has a TON of syntactit niceties today. Most of the warts are no longer relevant (here's to a future "use strict 2" mode that eliminates the really bad type coercion bits).
It hits a very pragmatic center where you can do OOP, but top-level functions are also possible (looking at you Java).
JS also has good functions. Closures that don't require spelling everything out, first-class functions that can be passed around to other functions, anonymous functions, etc. Outside of currying, it has almost everything you'd want functions to have. That's something sorely missing in languages like Python (lambdas), Ruby (proc/lambda/block mess), PHP (no implicit closures), Java (no first-class functions), etc.
Only, you know, without List Processing. And seventeen things that test false. And all numbers being floating-point (except when optimized implementations fudgily pretend that small values are integers). And a switch statement that you have to put into a function in order to get a return value out of it (everything returns a value in Lisp). Maybe it's more of a Scheme in Algol clothing; not everything returns a value in Scheme.
Scheme adapted lexical scoping and block structure from Algol 60. Lexical scoping was a major departure from LISP in 1970s. Call/CC was not in the original Scheme. Probably added in R4RS. Macros came later too. My point was that calling JS Scheme in Algol clothing even less valid than calling Scheme Algol in LISP clothing!
I never read this book during my education. Would you all say there’s something to be gleaned from reading this as an engineer with some years of experience already, or is it primarily an introduction to programming?
If you are already a programmer and mostly want to check it out for ideas and concepts, you're better off with the Scheme version. The book itself is not 'primarily an introduction to programming', at least not in the conventional sense.
It is introduction to programming like no other. It takes it to a whole new level. Mind you, it is not about software engineering just about how to solve problems with algos. It used to be in LISP (Scheme if i am not mistaken) and now they have a JS version.
I have a copy of SICP right next to me, and it's one of my favorite computer science books. I'm not sure how I feel about this javascript edition though...
test failed for a zero-valued x; had to make it !==. WTF? I never want false and 0 to be other than different objects.
Almost every twist and turn in this language is an imbecillic clusterfuck.
I had one instance of a if (foo.bar = 0) typo; no warning from the implementation. Why do we want stupid C mistakes in a higher level language, without the C fixes for them?
list.forEach(function (item, i) {
...
});
makes my eyes bleed. JavasSript borrows the syntax of languages geared toward a form of computing at which JavaScript is poorly suited for; when you're combining functions together, it looks like dog's breakfast.
To get a value out of a switch statement, you have to wrap it in a function that is immediately called?
function () {
switch (WTF) {
}
}();
the switch statement has the idiotic break with fallthrough found in C.
One redeeming feature: A || B || C ... works a lot like (or A B C ...).
Unfortunately, this is required for the simple task of incrementing a nonexistent dictionary key from zero:
dict[key] = (dict[key] || 0) + 1;
this calls for two dictionary key lookups. You want this sort of thing in a single operation.
Oops! In the following, j isn't lexical:
for (let i = 0, j = 0; ....
I copy and pasted something like this from a StackOveflow answer into a recursive function and spotted that the j in a recursed frame was messing with the j in outer frames, there just being one j.
What is "function scope" and why even have something so idiotic? Just let, or GTFO! C has function scope for goto labels, that's it; why introduce something like that.
Appending together Lists has functional semantics, like Lisp:
a.concatenate(b) // new thing is returned, a stays the same, so you must do a = a.concatenate(b).
But
a.push(b) // mutates a in place.
Here is a shitshow, curently with a fitting number of upvotes: 666.
code was silently skipped even though foo.bar is an empty list. No diagnostic, nothing. Oh, indexOf is a function, which is an object. Every damned object is a dictionary with properties you can access as strings with square bracket indexing, and that is always safe: it yields undefined if the property is not found.
I feel your pain, but these mostly fall into the same category of frustrations you'd get when doing something like switching operating systems, e.g. going from Windows to Mac or vice versa. You run into all kinds of things that seem stupid and you're bombarded with things that should "just work" and seem not to.
But, they disappear and are mostly non-issues once you use the alternate system/language regularly for a while. Some I think you will come to see as benefits, sensible, or find that there are trivial workarounds.
One quick improvement. The more commonly used contemporary syntax in JS for something like your list.forEach example is: `list.forEach((item, i) => ...)` —writing out the full `function` keyword is indeed an eyesore :)
I don't think I'm correct here. However, be that as it may, in my code, when I moved that second variable before the loop and initialized it there with a let, the behavior somehow changed.
If forEach is not to be used, it should be deprecated and removed from the language in a timely manner.
The objection that it's not functional is invalid; procedural traversal with a callback function, object or closure, is a time-honored pattern in software.
Most of the other problems cited are not with forEach per se but the language in which forEach finds itself.
None of the alternatives presented are more attractive than
for (let i = 0; i < obj.length; i++) {
let elem = obj[i];
}
If we could just run this through the C preprocessor, we could have
foreach (i, elem, obj) {
}
by way of something like:
#define foreach(ivar, elvar, obj) \
for (let ivar = 0, elvar; \
ivar < obj.length && (elvar = obj[ivar], true); \
ivar++)
Since Javascript is based on C syntax, it should have the preprocessor that the birthplace of C saw it fit for that language not to be without.
That source is simply incorrect. forEach is perfectly fine to use as long as you realize what is happening. It iterates an array where each thing is a function that returns a promise. Of course the results aren't what they expect.
ES5 array additions suffer from being a little too early (though later stuff wouldn't exist otherwise, so...). They are designed to deal with holey arrays (arrays with indexes missing). This is extremely uncommon today, but was decently common once upon a time. They were also created before the iterator protocol.
The real fix is to design iterator versions that can handle things like async generator functions.
> it should be deprecated and removed from the language in a timely manner.
NOTHING can be removed from the language once added. Doing that would break all the older websites that depend on it (technically, a few minor breaks happened after they tested millions of sites and couldn't find anything that was adversely affected). At best, they can block older features from newer features. For example, using class syntax or a bunch of other ES6 language structures automatically makes your code shift into "use strict" mode.
I hope they introduce a "use strict 2" variant that strips away more of the undesirable features than the current "use strict" does.
> Since Javascript is based on C syntax, it should have the preprocessor that the birthplace of C saw it fit for that language not to be without.
That pre-processor was a source of untold nightmares. Direct injection leads to bugs. If someone is going that route, full-blown macros are the only answer. There is a full-blown macro system Mozilla created a few years ago, but it's not very popular.
There also exist some C-style pre-processors for babel too, but they should be avoided because lisp's gensym is a critical feature that they and C both lack.
Some of this is a lack of familiarity. You wouldn't dream of using C without reading a book first. You wouldn't dream of tackling a Java project without reading on the topic. You wouldn't even dream of trying something as simple as Python without a bunch of reading.
Why do you try to code JS without reading about the language? It's the only language where people consistently try this kind of thing.
> false != x
Referential equality operator is `!==` (which isn't so different from lisp where you have eq, eql, equal, string=, etc). Fun fact: Eich didn't have type coersion in the language at first. It was added at the insistence of developers (something he regrets, but Microsoft forced this to stay in the spec)
> function () {
switch (WTF) {
}
}();
A lot of languages don't make switch statements into expressions. The fact that JS can do this via a function while most other popular languages cannot do it at all is a benefit if anything (there is a proposal to allow these to be expressions). More generally, prefer this
let foo
switch (abc) {
case "blah":
foo = 123
break;
}
> the switch statement has the idiotic break with fallthrough found in C.
It actually gets this from Java. Eich was told to make the language look like Java and he did.
> dict[key] = (dict[key] || 0) + 1;
Your code is potentially bad. `||` uses "falsey" values. If the key is zero, the left-hand side will return false forcing the right-hand side. Use `dict[key] ?? 0` instead as it will only return the right-hand side if the left side is `undefined` or `null`.
If it is a pure dictionary, you are using the wrong construct too. Use `Map` instead
let dict = new Map()
dict.set((dict.get(key) ?? 0) + 1)
> this calls for two dictionary key lookups. You want this sort of thing in a single operation.
You get a value rather than a reference. I believe the JIT can optimize this pattern. If you are really fixated on using a reference, store an object with a value instead so you can grab and keep an object reference.
> What is "function scope" and why even have something so idiotic?
In lisp, you'd use `(let ((foo 123)) (print foo))` to set an explicit scope. ES4 proposal had a let block which I think would have been a better idea than the current stuff. Function hoisting generally occurs in other languages one way or another, but is hidden from the user.
> for (let i = 0, j = 0; ....
Yep, this is one more reason why the correct fix was a let block with `let (i=0, j=0) for (; ...)` or 1et for (var i=0, j=0...`
> Appending together Lists has functional semantics, like Lisp:
Array.prototype.concat goes back to at least the ES3 spec as does Array.prototype.push. At the time, arrays were either implemented as hashtables or linked lists. My guess is that adding a single entry to the hashtable only occasionally caused a resize while concatenating N lists was almost guaranteed to do so. I think they decided that it was easier for implementors to just create a new reference.
Since ES5, most new array APIs have favored returning new lists for everything. I think there's a case for making new versions of those older APIs that behave immutably.
> if (foo.bar.indexOf[key] < 0) { code }
This could happen in any language with a methodNotFound handler. For what it's worth, `if (foo.bar.contains(key)) {code}` would be the better way of writing it. I think Typescript would also catch this (though I'm not 100% as I don't remember ever having made that particular mistake).
> Every damned object is a dictionary with properties you can access as strings with square bracket indexing, and that is always safe: it yields undefined if the property is not found.
Garbage In; Garbage Out. At least it handles that garbage in a safe manner. What more could you ask from a dynamic language?
> Why do you try to code JS without reading about the language?
Because it's painful drivel.
> Your code is potentially bad. `||` uses "falsey" values
We know that this dictionary will only contain things to which we will be adding 1, which we expect to be numerically done.
(I have already made the point that there shouldn't be multiple falsey values.)
I mean, there could be other problems in this direction: if a string is in there like "foo", then the + operation will make it "foo1".
It's unlikely we would ever want to code a "add 1 to dictionary element, treating non-existence as zero" which will work for absolutely any dictionary regardless of knowing/assuming anything about its content.
> This could happen in any language with a methodNotFound handler.
Only if you define that handler, and have it silently return.
Handlers for method not found are a very poor idea to include in an object system. Ruby and its ilk uses it for ugly, monkey patched solutions for this and that.
I will preface this with, "I absolutely love SICP", but I think there are better places to go for everything it teaches. In terms of breadth and information per page ratio there's almost nothing better though.
- For how to actually program: How to Design Programs [1]
- For how to write programming languages: Essentials of Programming Languages [2]
- For how computers actually work: The Elements of Computing Systems (nand2tetris) [3]
I am a huge fan of How to Design Programs. It does a better, more thorough job of teaching the "core skill" of programming SICP or anything else I've come across. Its text is better paced (although very long), more approachable, and more focused on teaching how to program (as the name suggests). On top of that it has an absolutely massive number of exercises that do not require an interest in basic number theory to enjoy.
You will know how to actually solve new problems with programming when you finish HtDP (or are even part way through).
The really interesting parts of SICP are the 4th and 5th chapters any way right? Well, Ch. 4 (about "metalinguistic abstraction" or how to modify your interpreter to give your language different properties) is incredibly densely packed with information. It covers an interpreter for all of this in about 100 pages:
- lazy evaluation by default
- nondeterminism
- aka search as a language primitive
- the basics of continuations (but not accessible to the user)
- propositional logic
- aka declarative (what is) vs imperative (how to) programming
It's a great appetite whetter, but it'll leave you wanting more.
If this stuff interests you Essentials of Programming Languages is a much better resource. It follows a similar style to the approach in SICP. You are walked through building interpreters for increasingly more complicated languages. Here are some brief descriptions of some of features you'll learn to add to the LET language (a very simple purely functional language that serves as the core, you'll also write this too):
- Mutually recursive procedures
- Static code transformation to use lexical addressing
- Mutation (and how to perform call-by-value, call-by-ref, and call-by-need languages)
- Continuation-passing vs Environment-passing interpreters
- Advanced control structures like exceptions and threads
- Static types and type inference
- Typed modules (I've not seen any other introductory text do this)
- Object-orientation
It contains a plethora of great exercises that ask you to extend the languages in interesting ways or to reflect more realistic demands of a language.
For chapter 5, Register Machines, I think we all know that the first half of Elements of Computing Systems (colloquially nand2tetris) is a great resource for that. The second half is excellent as well, but it's outside of the SICP scope.
Summary:
;; Page Count 1374 = (+ 792 410 (/ 344 2))
In total I just recommended 1374 pages to replace 657 pages in SICP, so it's double the effort. And to be honest, if you're motivated enough to read all that then, you'll probably still read SICP if for nothing else than historical interest. If you start with SICP though, you'll still get a lot out of all the recommendations though!
https://sicp.sourceacademy.org/ (JS version)
https://mitpress.mit.edu/sites/default/files/sicp/index.html (1984 classic)