Python so far

It looks like I'll finally have to learn Python thoroughly for a project I'm working on. Shouldn't be too hard. I was recommended Dive Into Python as a reference. It seems OK, and it's great that the authors and publisher have made it freely available online. I have to say, though, that the farther I got in it the more I wished I had been allowed to do some editing. (B)

Mostly, Python seems fine. Obviously, I'm tainted by the superiority of Nickle (B), but in many cases Python takes similar approaches to Nickle.

However, there's a few things about Python that bug me so far. In the interests of getting them off my chest, I'm taking some notes here, in the order I encounter issues in the text referenced above…

  1. First of all, lack of static types is a huge hole for me. Cf Nickle to see how you can have them without causing much grief. Static types sometimes turn run-time errors into compile-time errors. Enough said.

  2. Similarly, the whitespace thing. I understand the vague attractions of this approach, and don't really want to debate it there. I use Haskell's version of it all the time. I don't like it. In the first example in the reference, gratuitous backslashes are added to ends of some lines because the author finds it "easier to read". Cognitive dissonance, anyone?

  3. Lists are shared, and list operations are destructive. Sorry, that should have had an exclamation point, and some emphasis—let me try again. Lists are shared, and list operations are destructive! There, that's better.

    We found out with ancient Lisp dialects that this combination just begs for wacky, hard to fix bugs. There are some obvious alternatives: (1) make lists non-shared (but this leads to a lot of gratuitous copying); (2) make list operations non-destructive (still some gratuitous copying, and some gratuitous pain for programmers); (3) make lists non-primitive and let the programmer deal with it (which in its extreme form is a total pain); (4) provide some safer but less flexible datatype than lists, usually growable vectors (Nickle does this, but constant-time list insertion etc is sure missed sometimes).

  4. There's a bunch of introspection. I am always suspicious of introspection—it's a great way to introduce bad bugs and runtime surprises. It's hard enough for me to code, much less code at the metalevel.

  5. Lambda isn't general! (There, I got the emphasis the first time this time.) I know Python lambda is a compromise between the language designer (who hates it) and users (who want it), and I know that in an object-oriented world lambda can be kind of weird. That said, if you're going to put it in, it should be first-class. Nickle gets this one so right: In Nickle

    int inc(int x) { return x + 1; }
    </pre></blockquote>
    is just syntactic sugar for
    <blockquote/><pre/>
    int(int) inc = int func(int x) { return x + 1; };
    </pre></blockquote></p></li>
    
    <li/><p/>"In Python, you can use either == None or is None, but is None is faster."  I hate nonsense like this.  The whole =/equal/eq thing is another thing we learned is a bad idea from trying it extensively in Lisp.  Most modern languages manage to get this right; it's the programmer's job to give the right semantics, and the compiler's job to pick a fast implementation of those semantics.</p></li>
    
    <li/><p/>The whole module import syntax is totally awkward.  It's not a big deal, but it's the sum of little details like this that drive me nuts.  Cf Nickle's quite straightforward distinction between loading and importing a module, and the whole autoload / autoimport mechanism that I developed to make this work.</p></li>
    
    <li/><p/>For an object-oriented language, its implementation of objects is sure clunky.  "__init__"?  Really?  The whole underbars thing is just weird.</p></li>
    
    <li/><p/>A class defined by multiple inheritance picks the first method with a given name from its ancestor list.  This is truly error-prone.</p></li>
    
    <li/><p/>WTF?  If you define a Python class, and then inadvertently assign an instance variable of that class that doesn't currently exist, it will be auto-created for you.  So<blockquote/><pre/>
    class C:
      def __init__(self):
        self.critical = "no emergency"
      def check_emergency(self):
        if self.critical == "emergency":
          print "emergency!!!"
    
    c = C()
    c.cirtical = "emergency"
    c.check_emergency()
    </pre></blockquote>prints&hellip;nothing.  Cool, huh.</p>
    
    <p/>To be fair, encapsulating all instance variables with setters and accessors will prevent this from happening.  Unless you typo a setter.</p></li>
    
    <li/><p/>"Python uses try...except to handle exceptions and raise to generate them. Java and C++ use try...catch to handle exceptions, and throw to generate them."  This is because Python hates us.  Of course so does Nickle, which inexplicably uses raise rather than throw, although it does use try / catch.</p></li>
    
    <li/><p/>Python support multiple character encodings, and the default is not UTF-8.  Instead, you prefix strings with a "u" to get them to be interpreted as UTF-8 strings &#x1D301;.  Nickle's way, just  using UTF-8 as its native string encoding for everything, is <em>way</em> better.</p>
    
    <p/>Just found this <a href="http://boodebr.org/main/python/all-about-python-and-unicode#WRINKLE_U"/>page</a> that claims that the length of surrogate pairs is mis-counted by Python, and the surrogate pairs will be auto-generated if your Python is compiled wrong and you're outside the BMP.  Yikes!</p></li>
    
    <li/><p/>Apparently those things that look like "first-class functions" aren't so much.  As near as I can tell, lexical scoping and dynamic lifetimes don't work right.  Consider this Nickle code:<blockquote/><pre/>
    int() factgen() {
        int prod = 1;
        int i = 1;
        int gen() {
            prod *= i;
            i += 1;
            return prod;
        }
        return gen;
    }
    
    int() f = factgen();
    for (int i = 0; i < 5; i++)
        printf("%d\n", f());
    </pre></blockquote>
    Works just like a functional programmer would expect, printing the first five factorials.</p>
    
    <p/>Now consider the corresponding Python:<blockquote/><pre/>
    def factgen():
        prod = 1
        i = 1
        def gen():
            prod *= i
            i += 1
            return prod
        return gen
    
    f = factgen()
    for i in range(5):
        print f()
    </pre></blockquote>
    This fails to assign to prod and throws an exception.  Further investigation suggests that while it's possible to read prod and i as expected, they are not in lexical scope for assignments.  Uggh.  Either you have first-class nested functions or you don't.  The existence of Icon-style generators helps, but does not eliminate this problem.</p></li>
    
    </ol>
    
    In summary, I guess my opinion is that Python is a quite usable language, when programs are kept simple.  It constantly walks the edge of the Perl abyss, though, tempting programmers to write 'innovative' programs that they later are unable to read or debug.  The lack of some basic software engineering supporting features seems like it would be a real problem.
    
    I'll let you know when I've written some real code with it.  Maybe I'll change my mind, but I don't think so.  All in all, the whole experience has made me very proud of Nickle. (B)