It seems like they have attempted to write scheme using JavaScript syntax to avoid having to make any significant changes to the non-code sections of the book.
Uh oh. That's like the original Numerical Recipes in C. All the arrays had their first value as 1, instead of 0. It was a conversion from FORTRAN, where arrays really did start at 1. The Numerical Recipes authors provided an array allocator which generated an address offset before the beginning of the array to make that work. It was awful. I rewrote the algorithms I needed with sane layout.
The whole point of SCIP was mathematical beauty. Losing that in translation is not good.
No. If you think that's the whole point, you missed the point.
Mathematical beauty was one of the ways to make the point, but far from the most important.
As a footnote, one of the changes which happened since publication is the victory of functional. Note that both JavaScript and Python now embody many of the principles, design patterns, and best practices from SICP.
As a second footnote, you're right: SCIP reads better than SICP. "Skippity do da..." (and if you want a third footnote, I know in the original it's zippity, but that's how kids in MY elementary school sang it, so to me, it will always be skippity"
There was a library adding macros to JS that was a thing long ago. I always thought that `cond` would make JSX way better, though JS + macros sounds terrifying in general.
I frankly find it hard to imagine an expression form of if...else that's more readable than either the JS or Scheme versions of the code.
In fact, I believe the problem with the JS example's readability is the formatting. Here's how I'd format the same code, and I find this quite readable:
Scala-style if expressions would be significatly more readable IMO (particularly if you also had the feature of allowing a block to be used as an expression).
I'd possibly make it even more syntactically simple by removing the braces for one-line if statements. I know this can be a bit controversial, but for cases like this where the block consists only of a return statement, and where that statement fits on a single line (and where you have a formatter and a linter that will prevent you from writing indentation that doesn't match the semantics), it's very useful for removing visual clutter.
That said, this is pretty much how I'd write it, and seems a lot simpler than the heavily nested ternary (and I say that as a fan of nested ternaries!)
It’s calculating the derivative. 0 and 1 aren’t used for their truthiness, but as values. d/dx x becomes 1 and d/dx y becomes 0 (the two cases, same variable and different variable).
So you've gone from "nested ternary is hard to read" to "This is an introductory text, let's mix arithmetic with numbers and booleans by using true and false for 1 and 0"? That's a remarkable turn in just a few comments toward the side of obfuscation.
In f# everything is an expression and it simplifies the language a lot. The last value in a block is considered the value "return" value of the whole block, which has the knockon effect of getting rid of most returns, allows you to check an if/else to return the same type and such. This also works in conjunction with the type inference, so you get an error when you try to return a string in one branch and an int in another.
I see this opinion voiced all the time, and I can never understand how people can struggle with something like nested ternaries. Surely a straightforward use (i.e. not a weird edge case) of very basic syntax shared by most widely used programming languages shouldn't cause much of an issue?
It should also be easier to reason about nested ternaries than an equivalent set of nested if-elses, because at least with ternaries you know that every branch is an expression resulting in some value (and statically typed languages ensure that these values have the correct type), whereas if-else blocks can contain anything, are likely (and in most languages (which lack if-else expressions), forced) to mutate things, and have no guarantee of producing the result that you were expecting, unlike ternaries (especially in statically typed languages).
Right associative! It's just one of the many rites of passage for people working with PHP to get bitten by its left associative ternaries. Naked nested ternaries are deprecated now, but maybe one day, PHP can have right associative ternaries.
And when is that? The docs just say it's right associative[1]. And while it's been a long time since I've written any nontrivial Perl, I don't remember ever having ?: be left associative.
Unfortunately there is no if-expression in JS so sometimes it's awkward _not_ to use ternaries in multi-line statements - for instance, when writing in an expression only context like a string interpolation or JSX. It's also just annoying to not be able to assign conditionally without using it, instead of a more clear and readable if/else. It's one of the more annoying nits of the Algol legacy.
Oh, but fully agreed, nested ternaries is right out. In fact, usually in those other cases too, it's just annoying that there's not a good alternative.
If you want the equivalent of if-expression in JS, I think it is much better to make a separate function with a number of returns.
In this case the function is already there:
function deriv(exp, variable) {
if (is_number(exp)) {
return 0;
}
if (is_variable(exp)) {
if (is_same_variable(exp, variable)) {
return 1;
} else {
return 0;
}
}
if (is_sum(exp)) {
return make_sum(deriv(addend(exp), variable),
deriv(augend(exp), variable));
}
if (is_product(exp)) {
return make_product(deriv(multiplier(exp),
variable),
multiplicand(exp)))
}
return error(exp, "unknown expression type -- deriv");
}
It's subjective, but seconded (switch statements are also great for this because they make fall-through logic more obvious).
I'll add a couple of things onto this: early returns are very helpful for me in avoiding nesting if statements (although that's less applicable in this specific example).
function op(cond) {
if (cond) {
//do something
}
}
function op (cond) {
if (!cond) { return; }
//do something
}
And it's good to remember that you can basically stick functions anywhere including inline, so it's not necessarily a requirement to take a function like this and move it to a top level as a private function. If you're only using it in one place you can just define it and call it anonymously.
And don't be afraid to still use ternary operators non-nested. There's a sibling comment complaining about the nested if statement. If that really bothers you, you can still do:
Refreshing to see some love for early return. Often people like to say they are an anti pattern, but then you have to maintain each layer of if nesting in your head (as they are sometimes off screen) when reasoning about code in the middle instead of handling edge cases first and leaving the rest of the method for the core/common case.
Get rid of the else in the is_variable(exp) block and this is a lot more readable than the nested ternary version and seems just as easy to transcode from Scheme. Darn.
It's not the biggest thing in the world, and I don't want to distract from the rest of the book, but this is a situation where writing a one or two line helper function:
const _ = (cond, a, b) => cond ? a : b;
would have made the code much more readable without much downside that I can see -- at least to my subjective opinion. Maybe I'm missing something.
Edit: comment below correctly points out that if it's important for you to avoid immediate evaluation, you'll need to wrap your conditionals in functions.
JS is not lazily evaluated so that means `a` and `b` would both be evaluated regardless of the result of the cond expression. To make a proper version you have to complicate things by calling it like this:
_(cond, () => a, () => b)
And _ becomes:
const _ = (cond, a, b) => cond? a(): b();
And it does matter in this case when looking at the last condition which signals an error (does not return an error value if I understand it correctly). In which case your _ would raise an error even when not appropriate.
That is an excellent point, thanks for pointing that out.
I'm not sure it matters here, the error you're pointing out looks to be getting returned (unless I'm misunderstanding what the book intends the `error` function to do), and creating an Error in Javascript is fine, it doesn't break your program until it's actually thrown.
Edit: just looked at your comment again, and you're saying it does actually throw the error rather than returning it :) So double-corrected on my part :)
But your point stands regardless. There will be scenarios where what you're talking about matters -- JSX also follows this pattern of immediate evaluation and yeah, I see errors from that plenty of times. So it's good to mention.
One thing I have learned from reading $any_lang code written by professional teams is: use nested ternary operations where it makes sense to do so, to tersely express a series of if/else conditions that simply return a value for each condition.
There is strict reasoning why ternary expressions are, in fact, logically better than a typical if statement.
The reasoning is because ternary operations eliminate programming singularities.
Example:
var x;
if (someExpression) {
x = True;
}
//var x is undefined
Example 2:
var x;
x = someExpression ? true : false;
// use of ternary expression prevents var x from ever being undefined.
The ternary expression forces you to handle the alternative case while the if expression can leave a singularity. A hole where x remains undefined.
Some people say ternary expressions are less readable. But Readability depends on your "opinion." There are no hard logical facts about this. So it's a weak-ish argument.
It is actual fact (ie not an opinion) that ternary expressions are categorically more Safe then if-statements. Therefore, they are factually better in terms of hard logical metrics.
This is the reasoning behind nested ternary statements. It's also one of the strange cases in programming where superficial qualitative attributes of "readability" trumps hard and logical benefits. The overwhelming majority of the population will in fact find many nested ternary operations highly unreadable and this "opinion" overrides the logical benefit.
Usually though, to maintain safety and readability I just alias boolean statements with variable names. The complexity of a conditional expression can be reduced by modularizing parts of it under a symbolic name, you don't necessarily have to reduce complexity by forcing the conditional into the more readable bracketed spatial structures favored by if-statements.
Yes technically the ternary expressions are still nested in a way here, but when reading this code you don't have to dive in deep past the symbolic name.
Imagine if you have a boolean condition that relies on multitudes of these sub expressions. By defining the final statement as a composition of Named ternary expressions you eliminate the possibility of a hole;... a singularity where one alternative wasn't considered. In my opinion this is the best way to define your logic.
If-statements, imo, should be reserved for functions that are impure (void return type).. code that mutates things and touches IO which is something you as a programmer should keep as minimal and segregated away from the rest of your pure logic as possible.
Example:
if true:
sendDataToIO(data)
//the alternative of not sending data to IO is not a singularity. It is also required... a ternary expression makes less sense here.
Last but not least: The ternary expression is also a bit weak because it only deals with two possible branches: True/False. The safest and most readable primitive to deal with multiple branches of logic is exhaustive pattern matching. Both Rust and Haskell have a form of this. In rust, it is the match keyword.
I implemented the Monkey programming language as described in the book Writing An Interpreter In Go. Over time I extended it to support different things, and one of the additions I made was to add support for the ternary operator.
In my implementation nested ternary operators were a parse error.
I think SICP and Scheme go together like horse and carriage, and neither Python, JavaScript or other "real world" languages should taint the insight gained from that wonderful pairing.
Because Scheme - being a LISP - has the same syntax for code and data, metalinguistic abstraction is facilitated. Nobody should have to read through boilerplate code to dig up the core message of SICP.
I took the Algorithmics I course based on SICP/Scheme in 1993 at the University of Erlangen, and although I never used Scheme or CommonLISP in production, it made me a better programmer regardless in what language. Thanks to Abelson and Sussman!
I think SICP would be just peachy in Elixir, but I’m coming around to the notion that keeping functional programming for functional programmers is a form of professional neglect.
Functional core, imperative wrapper is one of the few sane ways to build a large (Conway’s Law afflicted) system, and too many of my peers never studies SICP in school. They don’t even know the damage they do, and as we’ve learned in many, many other circles, vilifying people does not get them to change. It’s a tool of last resort, and most often used to publicly label someone for ostracism, not help.
So excuse me, but y’all are crazy. We need SICP for JavaScript developers more than we need SICP for every functional language in the world, combined. And JavaScript is only one corner of the programming world.
Write functional code in a language that does not force you to write functional code. Take the training wheels off. Live in the real world.
Actors in Elixir (and AFAIK the Actor Model in general) can mutate their internal state in response to receiving a message. It would probably be awkward to use actors in this way just to facilitate mutation though.
(define lol
(let ((a 0))
(lambda ()
(set! a (+ a 1))
a)))
Scheme is a functional programming language in the same way python is. The only difference is that it has tail call optimisation and the stack doesn't have an arbitrary limit on the number of function calls it can hold.
In most implementations neither Python nor JS (except perhaps partially in Safari) is up to the task of even running the code of SICP properly, due to not having things like TCO.
So the code for versions of the book written using those languages would need to be restructured, losing a big chunk of elegance and possibly destroying the actual lessons about how to express oneself as a computer programmer.
What I loved most about my class based on SICP was that I learned a whole new way to reason about code, and that came from using Scheme. They changed the intro class at Berkeley to use Python a while back, when the folks who knew lisp/scheme retired, and I think it made the class much worse.
I think the Scheme version of SICP will live on outside of academia and CS undergrad studies.
I'm using SICP for self-learning and I had the choice of Scheme, JS, or Python. I still chose Scheme.
This is because I figured Scheme would offer something those other languages didn't since Scheme is the original language of the book and Scheme is just "different" than other languages. It forces you to think differently about programming.
I'm glad I chose Scheme. SICP with Scheme is the best choice imo since Scheme most clearly illustrates the lessons of the book.
I actually used SICP when teaching a junior-level course on programming language principles and paradigms at San Jose State University, where the introductory language is Java. My students programmed largely in Scheme and Prolog, and they also got exposure to Haskell, Smalltalk, and Common Lisp.
I still believe there is room for SICP in the CS curriculum. While as an introductory language SICP and Scheme may lose out to more commercially popular languages like Python and Java, I still believe that eventually students should be exposed to Scheme as an introduction to functional programming and to demonstrate a minimal, highly flexible language as a vehicle for teaching programming language design and implementation. Then eventually the students can be introduced to the world of statically-typed functional programming languages like the ML family and Haskell.
What's deterring me from pulling the trigger on the Scheme version is the price. CAD$87 seems a bit steep for a paperback edition that's almost 30 years old. The JavaScript version is CAD$20 cheaper.
Even having them ship it to you will probably be cheaper than that CAD price. It's unfortunate the price has shot up so much, I guess I'll be taking very good care of my hardback edition.
Used books are awesome. I have north of 500 books and I doubt more than 50 were purchased new. Only that many because some of my relatives want Christmas and birthday wishlists every year, but won't shop anywhere but Amazon and think it's weird to buy someone a used book as a gift (I'd rather have 2-3x as many books for the same money, but hey, I'm not the one paying, so whatever, I guess).
SICP, as a book, is very nice. It's a great size, not too big, light and nimble to carry around. Many books, notably text books, are pretty big and ungainly. But SICP is just a really nice package. (Mind, I have not seen the paperback, I have a hardback from mutter mumble years ago.)
You know how people love to repeat that old saw about how computer science is no more about computers than astronomy is about telescopes (or whatever it is)? Well ironically (because it's the same exact people but this time on the other side of the misunderstanding) SICP is no more about scheme/lisp than computer science is about computers.
SICP is not about scheme or lisp, but its concepts are best understood in scheme in lisp. Just like the best way to understand what Saturn looks like is to look at it though a telescope.
Sure you can use a spectrum scan or the output of a radio telescope, but it's really not the same as looking at in a telescope.
Same thing here -- SICP just isn't the same if it's not in scheme or lisp.
I just really think you should be ready to accept the full force of your words:
>Same thing here -- SICP just isn't the same if it's not in scheme or lisp.
That straight up reads like SICP and lisp are inseparable and that exactly means that SICP is not as general as people claim.
What we have here is unstoppable force meets immovable object (otherwise known as a contradiction). The resolution is simple: either SICP is valuable for the ideas, in which case renderings in js or python are fine, or SICP's value is diminished without lisp, in which case it's not actually about ideas but about the language of choice.
> That straight up reads like SICP and lisp are inseparable and that exactly means that SICP is not as general as people claim.
They are separable, but there is a loss of elegance.
For example Scheme has linked lists and symbols built-in in an elegant way. The JavaScript version tries to provide them, but in a clunky way. Symbols are replaced by strings and the (a b c) notation of Scheme looks as ["a", ["b", ["c", null]]] or list("a" "b" "c") in the JavaScript version.
That a text may make use of the language it is written in, that should not be surprising. Thus the language shapes what and how it is said.
Oh come on, would you reckon that SICP would be just as good in Assembly? Just because computation is substrate independent doesn't mean our brains don't care about the form computational concepts are expressed in.
i have a pretty simple perspective on SICP and lisp: neither are that amazing and both are idiosyncratic examples of a very generic thing (ds&a book and programming language, respectively) and also both attract a cargo cult following.
You have your causation backwards. SICP has valuable ideas in it. But those ideas are best understood using a particular tool.
You can use other tools to understand it, but it will be more work and you won't understand it as well.
Just like you could use a screwdriver to drive a nail into a board. But it will be easier and better understood if you use a hammer. The nail provides the same value either way.
I've tried to go through the JS version, and I have to disagree. I know enough Scheme to see why the book is structured the way it is, but it doesn't work well in JS. The patterns that seem so natural in Scheme are contrived to the point of being distracting in JavaScript.
I started this book with coworkers and it fell really flat.
I haven't read the original, but knowing Scheme I could see why the book would be structured the way it is. In JavaScript, though, it really just doesn't work. The code is horribly non-idiomatic for JS, and because JS isn't actually Scheme the order of the concepts introduced doesn't make sense. When it does talk about JavaScript, it's condescending towards it in ways that were off-putting to my coworkers (we're a TS shop), which is weird given that it's a book in JS.
Someday I'm going to get around to reading the original, but I can't recommend this one.
Thanks for the heads-up - I'm not going to bother with this piece then.
Anyone with strong opinions about mainstream programming languages is automatically suspicious to me.
I mean, I get it - plenty more elegant and better thought-out languages than JavaScript, with their practitioners pounding the floor, saying "it should have been me - not him!".
But, ultimately, who can't write good code just because they're not using their favourite language?
not just by the literal text "Did you mean" that you used, but also due to being like Google in telling us what and how to think and read, or in (mis)interpreting the meaning of what we type.
You can edit your posts instead of replying to the same person multiple times and to yourself. Within the first 2 hours (hour and a half?) there is an "edit" option on your comment (this is also true for submission titles).
Heh, I took this class back in August 2018 where it originated, at the National University of Singapore (NUS), CS1101S[1] taught by Dr. Martin Henz.
I was someone who thought he knew all about computers because I could program a tiny bit of C++ and Java, and thought this class would be a piece of cake. I couldn't have been more mistaken. The class kicked my arse so badly. I remember my score for the midterm: 34/75. I ended up getting a B– for it. I'll be honest, I hated it at first. Where were the loops, where were the arrays? Why was everything involving recursion? Why did some recursive code give rise to iterative algorithms, and others, recursive ones (the difference is tail-call optimisation to give constant stack space versus some growing stack space)? Why were lists this weird nested square bracket syntax???
The irony is that in my fifth year last year, I ended up becoming a teaching assistant for the very same module itself, because I realised how useful the module would be. There was truly a little bit of everything: from algorithmic complexity, to data structures, to even a bit of compilers and garbage collection, with the environment model of execution, meta-circular evaluator at the end of the textbook, respectively.
I had to prove to the lecturer that despite my rather lousy grade in the module itself, my experience in everything else counted as something. A compilers class that was conducted entirely in OCaml was a great refresher on functional programming techniques, as was my internships where I learnt C#'s LINQ. I read the textbook end-to-end while preparing to teach the class—something I ought to have done as a student in the first place. My opinion of the class has changed drastically: it's one of the best introductory CS modules there is, and the fact that NUS chose to use SICP as a textbook is one reason why its CS department has skyrocketed.
I have no comment about emulating Scheme with JS. I neither like nor use either language.
Why does everything have to be Javascriptified? Scheme is a big part of the SICP experience and trying to do it in another language you are losing a lot. Do people learn Javascript when starting their web development career then refuse to learn anything else? Getting stuck with JS in the browser is an artifact of history and given how bad the language is we should avoid spreading it yet the opposite is happening. Funny for an industry that's allegedly about doing things as smart as possible the lowest common denominator dominates.
>Funny for an industry that's allegedly about doing things as smart as possible the lowest common denominator dominates.
Yes.
The key word here is allegedly.
It's like how people rue the fact (a meme by now) that the (self-described) allegedly smartest minds in the world are desperately busy trying to, guess what, make people click more on ads(!), while largely ignoring ethics like giving people a choice about it, via opt-in rather than opt-out, confusing legalese in Privacy Policies and Terms and Conditions, fine print, etc.
If you want to work through SICP in Scheme, but don't have an easy way to run MIT Scheme, we made a compatibility thing, so you could do SICP using Racket (with your choice of editor/IDE, including DrRacket):
The early sad parts of "SICP in JavaScript" are especially section 2.2.1 Representing Sequences and chapter 2.3 Symbolic Data:
"All the compound data objects we have used so far were constructed ultimately from numbers. In this section we extend the representational capability of our language by introducing the ability to work with strings of characters as data."
In SICP there were Lisp's symbolic expressions (s-expressions). They were removed without even mentioning them...
On my phone I just get a drawing of an alien spaceship? I tried loading it through an archive (and I do get the front page of a web book) but none of the links work there.
At no point in my entire existence would I subject myself to JavaScript voluntarily ever again. I would write my own damn scheme environment and use that instead of this.
I don’t know if anyone has bothered to point this out but you could keep your schemey SICP and go clojurescript if you wanna run on JavaScript but then again you still have to contend with cycles and reverse polish
And Scheme, but the code in this book makes clear the limits of that influence (or, perhaps, proves the idea of “the medium is the message” in the context of language design.)
Here is an excerpt, it's not good:
Here is the original code: