As a fan of Python, my reaction to seeing this course was: "oh cool, a course on interesting things in a language I'm comfortable with".
I was then somewhat discombobulated to see the Python bashing in the comments.
As a fan of Python, my reaction to seeing this course was: "oh cool, a course on interesting things in a language I'm comfortable with".
I was then somewhat discombobulated to see the Python bashing in the comments.
Scheme is a good language for SICP because it's simple. You can build a Scheme interpreter as a class project. You can analyze it formally. Etc.
Python is a good language because it's readable and writeable. But it doesn't work for SICP since it's too complex for that. Python also intentionally omits things critical to SICP (like tail recursion).
Calling this book "SICP in Python" would be like taking your favorite poem, releasing it into a different language, and finding that the translators wrote a completely different book, with a different theme, to make it rhyme and the rhythm hold. Just something different with the same name.
(It is still not the good fit for SICP, but that’s a different conversation)
Most of the issues given on the linked-to page are not simple issues of readability; they are real pitfalls. A lot of the examples are actually readable. You will not easily avoid every single one of those pitfalls if you're coding in Python, even if you lint the code.
def fun(listarg = [])
listlocal = []
listarg.append(3)
listlocal.append(3)
It turns out listarg is bound to a list which is not freshly instantiated each time the function is called (with no corresponding argument), unlike listlocal. The expression is evaluated at the time the function is defined, not at call time. The value is stashed somewhere and that value is used for initializing listarg by default.I learned about this from ... running pylint3 on some code which found a buggy use of such a list.
This is probably that way for performance because Python doesn't have true literals. [1] is more like (list 1) in Lisp; it's a constructor that has to be executed, producing a newly allocated object; it is not like '(1) which is just a literal object that can be embedded into the compiled program image. Python literature incorrectly refers to [] as a literal, which is bad education: a disservice to newbies who deserve to understand what is a literal. The fact that you can do "x = []" and then safely append to to it proves that it's not a literal, because literal is an abbreviation of "literal constant", which is also something newbies should be taught.
Students of CS must absolutely learn the crucial difference between variable initialization and assignment. Python conflates the two.
x = 42
def fun():
x = 43 # defines and binds local x.
This was not even fixed in Python for a long time; now you can assign to the global one with a global statement. The concept is bad here and damaging to newbie brains.After taking a course which used SICP, I was so inspired I wrote a functioning Scheme interpreter in a couple of evenings - in C, with no external libraries. It was pretty limited (no tail recursion), but could run examples from the book.
That is not going to work with python - even just parsing the program will need a whole bunch of extra knowledge.
Given:
def extendList(val, list=[]):
list.append(val)
return list
What do the following print:print(extendList(1))
print(extendList(1))
print(extendList(2))
print(extendList(3,[]))
I do not blame them. This sort of behavior is error-prone. You can make sure that you do not use such a code in production with code reviews but it would be also great to not having such things in the language.
The answer is:
[1]
[1, 1]
[1, 1, 2]
[3]
It relies on knowing something about how python applies default arguments.
I’ve only written about a hundred lines of python in my life, so possibly I just got lucky - still, I would have thought an actual Python programmer should get this?
This prints
0
1
#include <iostream>
using std::cout;
using std::endl'
int z;
int foo(int x = z)
{
return x;
}
int main(void)
{
cout << foo() << endl;
z++;
cout << foo() << endl;
return 0;
} default = 10
def foo(x=default):
return x
default = 20
assert foo() == 10
I think tat makes a lot of sense.It could also be that the default value is a more ocmplex expression, we could use a function to generate a default argument.
def foo(x=create_default(y, z))
By evaluating it at definition time we only have to evaluate the expression once and not have this weird lazy expression.And yes it is used, for example the fastapi Dependency Injection system has been build quite cleverly using the ability to construct default values
https://fastapi.tiangolo.com/tutorial/query-params-str-valid...
Yes it is one of those things you just have to learn, but there are tons of stuff like that in most programming languages.
wildly_changing_variable = random_initializer();
stable_snapshot = wildly_changing_variable
def foo(arg = stable_snapshot):
...
Don't touch stable_snapshot and everything is cool. This is the rare case. Of course def foo(arg = wildly_changing_variable)
means we want the current value.I don't understand your "closure" comments; either way, things are being lexically closed. It's a question of when evaluation takes place, not in what scope or under what scoping discipline.
In fact, Python's treatment requires the implementation to have a hidden storage that is closed over; the equivalent of my stable_snapshot variable has to be maintained by the implementation. The definition-time evaluation has to stash the value somewhere, so to that it can use that one and not the current.
The default isn't a literal value when it is [], by the way.
If [] were a literal, then this would not be safe or correct:
def fun():
local = []
local.append(3)
The fact is that whenever [] is evaluated, it produces a fresh list each time. It's a constructor for an empty list, exactly like set() for an empty set. It just has slicker syntactic sugar.Python just calls that a literal because it looks like one.
Looking is being, in Python. Except for all the pitfalls.
(You’ll likely want to disable some of the more opinionated checks, but this is well documented and supported)
Python (go, rust...) aren't minimal, nor abstract enough to achieve above. For example, most of the SICP problems can be nicely solved in python, but if you are going to add evaluator, you'll need to pull parser/ast modules, which are story on it's own.
The web version appears to work though: http://sarabander.github.io/sicp/html/index.xhtml
verb [ T ]
UK
/ˌdɪs.kəmˈbɒb.jə.leɪt/ US
/ˌdɪs.kəmˈbɑː.bjə.leɪt/
informal mainly humorous
to confuse someone or make someone feel uncomfortable
When I took CS in school, these were not the central themes. On the last day of class the teacher showed us a short program in this funky new language called “Java” and we all had a good laugh at how it tried to make everything about objects, even where it made no sense.
Even if you’re only trying to teach object-oriented programming, I can think of better languages.
At the end of the day your average programming job at your average company is “all about classes, getters and setters, inheritance and so on”, and if you want to prepare students for that, you should probably focus on that.
I find much the same to often be true for many Java programmers. Programmers who start out with a course all about classes, getters and setters, inheritance and so on often end up with their minds wedged such that they never learn to program properly.
Java has one way to do everything. That's reasonable if you're running a large IT department and want programmers to be interchangeable between projects. That's a horrible way to have people understand the richness of computation.
You want new students to learn at least two ways to do abstraction and to structure code, and ideally, to learn many. Then, if they do Java for a random bank or something, they'll see where Java is on the spectrum. If you start with Java, you end with a closed mind.
Dijkstra was good at many things, but there's no need for an argument from authority when we have actual data and not just opinions made to sound stronger because they are phrased more aggressively.
Or to put things more accurately, he exaggerated for humor. It's a communications style. He was wrong if you read it too literally.
If you read him less literally, the basic point he was making was right.
If you start out in a language like BASIC, plenty of programmers never make it to the other side. And it applies much more to Java than to BASIC. With BASIC, everyone who goes into programming will move on at some point (there aren't BASIC jobs out there), and they'll be forced to learn something different. With Java, plenty of people learn it, work whole careers, and never know any better. That doesn't just prevent them from coding in Ruby on Rails or whatever else -- it makes them worse Java programmers too. They understand WHAT, but they don't understand WHY. They can't reason about things like abstraction from first principles.
You want to start out with a broad view of computation. From there, you then want to narrow. Java is okay for a junior-level course on OOP, but it's really, really lousy for a first exposure to programming, or a freshman course.
(I started out in BASIC too).