Wednesday, May 16, 2012

It's probably been done.

I started programming in junior high, writing BASIC code on a Commodore 128. At the time the only learning resource I had was the owner's manual and some issues of Compute!'s Gazette, so I was almost completely self-taught. I don't think I had actually heard the words "computer science".

If you've never programmed in BASIC of that era, understand that:

  1. There were no "functions"; a program consisted of a list of steps, executed linearly, with some control structures sprinkled in and plenty of GOTO jumps
  2. Variables were all global
  3. Variable names were limited to two letters
There was a limited bit of "subroutine" support: GOSUB was like a GOTO that remembered where it came from, and a RETURN would start executing from the next line after the GOSUB.  But since all variables were global, subroutines couldn't be reentrant.

So I got the clever idea that instead of using variables in a GOSUB routine, you could use arrays, and then have a variable that kept track of how many times you'd entered the subroutine. Sure, you had to define the maximum number of times you expected to GOSUB before you started returning, but at least you didn't have to remember whether or not the variable you were about to mutate was going to munge a previous call.

It was brilliant.

And when I got to college I learned that I'd "invented" the call stack and recursive functions. Rather a let-down, that.

So, grab your 8502 and load up a JMP 2011, when I wrote a blog post on "Stages of Competency":
After doing "this programmer thing" for a few years now, I've noticed a pattern in how I acquire skills and techniques. It's surprisingly consistent, and consists of these stages:
I thought it was pretty remarkable how consistently I saw the progression from "awareness" to "familiarity" to "functional understanding" to "understanding" to "competence" in myself and in others.

Pretty insightful, huh?

Turns out that pattern is a simplified subset of an educational classification system called Bloom's Taxonomy.

And it was first proposed in 1956.

So, yeah, that insight you thought you just had? It's probably been done.
 


Friday, November 11, 2011

3 Simple Rules That Will Make You a REAL Superstar Developer


In my experience there are two kinds of "rock star" software developers.  There's the Neil Peart rock star developer, who combines a natural blessing of talent and intelligence with a relentless work ethic and humble attitude, and over time becomes the developer that people not only want to hire, but want to be.  And then there's the "prima donna" rock star developer, who combines a modicum of raw talent with sheer attitude and self-promotion into the programming equivalent of a hotel-room-trashing, sex-and-drugs-and-rock-and-roll tabloid icon (I won't name an equivalent musician--use your favorite example).

Last year a tweet by Zed Shaw pointed me to a brilliant piece of satire called 3 Simple Rules That Will Make You a 'Superstar' Developer that gave three simple rules and two deeper principles for becoming a hard-living, Type 2 rock star programmer.  It's remarkably concise and accurate.  But what struck me is how close the three rules and two principles are to rules and principles for becoming a real Professor on the Programming Drums.

"Prima Donna" Rule 1: Write lots of code.

Have to fix a small bug in an area someone else has written? Don't waste time trying to understand it or their motivations for writing it that way. Just rewrite the lot as you think it ought to work. Call it refactoring if anyone asks.

"Neil Peart" Rule 1: Read lots of code.

You will spend more of your career reading code than writing code. Learn how to do it well. That means doing it a lot.  Read code even when you don't absolutely have to, and understand it deeply even when you think a shallow once-over will tell you all you need to know. Have a spare half hour?  Read the last couple checkins from other people on the team, even if you don't need to. You will learn more about the system faster, you might find issues earlier, and you will probably learn something or see a technique you didn't know about.  Have a spare afternoon?  Find an open source project and start reading code. Copiously reading both awful code and good code will help hone your internal sense of the difference.

Prima Donna Rule 2: Write your code quickly.  

Touch lots of files, and include every one of them in the ChangeLog. Don't worry about accidentally introducing hard-to-find bugs; they'll actually help you later on, as long as they're actually hard to find. Avoid introducing trivial bugs.

Neil Peart Rule 2: Finish your code quickly.  

"Done" is a Boolean state, and work isn't done until you would be surprised to have to revisit it again in a few weeks.  Minimize your personal work-in-progress.

Don't let 90%-finished tasks rot outside source control in a local directory, because you will forget the details. Don't check something in, thinking "this will do for now; we'll get to hardening it later", because you *will* forget the details.  And it's not finished until it's tested to your team's standards, documented to your team's standards, and understood well enough that if you get hit by a bus on your way home tonight, someone else can take your place.

Prima Donna Rule 3: Don't take time to document your code.

And don't add little comments explaining potential pitfalls in modifying some of the less clear statements you've introduced. You don't need them--you wrote the code.

Neil Peart Rule 3: Document your code with a single-minded purpose.

Obvious code with only as much documentation as is needed is the Holy Grail. Undocumented and unclear code is as bad, but over-documented code can be worse because it becomes a crutch ("so what if the code is ugly: that's why I commented it!").

You already know that when you write code, you put your reputation on the line that it is correct.  But when you document code, you put your reputation on the line that not only is it correct and sufficient now, but it
will be correct and sufficient when someone looks at it down the road. So minimize your risk of shame. Boilerplate comments, rambling exposition, comments that duplicate the code, and commented-out code
left in "just in case" are signs of laziness hiding behind the mantra of "comments are good; more must be better!"

Behold, the Underlying Principles

Strikingly, each set of rules emerge from one technical principle and one social principle, and each set of principles is a mirror image of the other:

The Prima Donna Technical Principle: You're 10x as productive when you're working on code you wrote as on code you didn't write.  

So, maximize your opportunity to work in code that you wrote, no matter the consequences.

The Neil Peart Technical Principle: You're 10x as productive when you have full awareness and mastery of your environment.  

Yes, you can achieve that by always working on your own code. That will mean being so prolific that all the problems you're fixing are your own creations.

Or you can achieve it by having a deep and total understanding of as much of your team's project and tool set as possible.  And the cleaner a design or a development process is, the more of it you can fit in your head at once.


The Prima Donna Social PrincipleYou win The Game by improving your reputation to superstar guru levels.

Your programming ability is judged by how much code you write, how quickly you finish features and fix critical bugs and how often your insights are necessary to solve problems.

The Neil Peart Social Principle: Optimize your life for value, not perceived ability

Your value to your project and your team is only partially related to your programming ability (perceived or real).  It's directly proportional to your ability to add value to your project and your team.  The more deeply you understand your project, your team, your code, and your tools, the more value you can add.  Conversely, any technical debt you create will be repaid either by you or those who follow you, and servicing that debt reduces the mental "capital" you have available toadd value.

In many ways, the "prima donna" and "Neil Peart" principles differ only subtly.  Maybe that's why it's so easy to find yourself on one path, when you really think you're on the other.


Wednesday, October 26, 2011

Mojo Reset (or, The Cunning Plan)

For the past few months, I've been enjoying Mike Pirnat's monthly "mojo checks"--monthly checkups that turn weak "New Year's Resolutions" to like-a-boss mojo maximization.

This coming month (November), I'm starting the same thing. Why November? Because I can, dammit.

And I don't want to wait until January.

And November is, of course, the mojo month, Why? Because this one goes to 11.

A Target-Rich Environment

So for the past two weeks or so, I've been working on The Cunning Plan: the list of things I'm going to target for maximal mojification. It includes 16 targets that divide roughly into three categories.

Professional

This covers both day work-related and non-work-related but professional targets.
  1. Obey the checklist. I'm not a naturally organized person, which means that when I'm really on the hook for something I have to compensate. One of the tools that I've found works well is a simple checklist for things that have to happen the same way every time. I learned this doing stage crew for a local theatre production--if I don't write down every pre-curtain, intermission, and post-show prep step, on paper, and then run down the list, I'll miss something. Similarly, there are things I really should do the same way every day when I start work: check builds, check for important email, look over today's schedule. The actual steps aren't important (and may change over the year); using the checklist consistently is the target.
  2. Speak twice at work. I'm at the career stage where that's expected of me. I don't mind doing it, but if I don't actively look for opportunities, it won't happen. Anything with an audience of more than one team will suffice. Last year I did twice, so that's what I'll shoot for this year.
  3. Do a lightning talk at PyCon. I had a pretty decent lightning talk in mind last year, and I forgot all about it until the last day. (Boo). I don't know what, but I can do something this year.
  4. Attend PghPy 6 times. Like Mike, I don't have a good excuse for not attending the local Python users' group. It used to conflict with my daughter's voice lessons, but that's changed, so now I have even less of an excuse. 50% attendance isn't that hard.
  5. Release one Android app. I've started three, got each to some level beyond "Hello, World!", and then neglected to take any of them further. So the easy stuff, most of which is done for you by the framework or comes straight out of the tutorials, I can do--but I don't have much feel for the real meat of Android development. I need to get one app sufficiently finished to the point of releasing it publically in some minimally-useful, 1.0-level form. I don't know what it will be, but after all, I have a whole year to do it.
Personal
  1. Blog monthly. Like most of the blogging world, I keep saying I'm going to do this, and don't. And it would be cheesy beyond cheesy to include these mojo check posts, so this really means twice per month.
  2. Read 50 books. In 2011 I decided I was going to attempt 100 books. That's turned out to be wildly optimistic. I'm on track to come in just under 50, so 50 seems like a good goal for next year.
  3. Miss no birthdays or anniversaries. Outside of my own nuclear family, I'm terrible at this. I can remember my kids' and wife's birthdays and my own anniversary, and I've never missed one of those. But I have to think a bit to remember my siblings' or parents' birthdays, and don't even ask me about my in-laws'. So for the next year I'm going to acknowledge (i.e., a present or at least a card, neither of which is a zero-day panicked Amazon gift certificate) all birthdays and anniversaries for my parents, my in-laws, my siblings and their families, and my siblings-in-law and their families. I mean, this is 2012. We have Google Calendar. There's no reason to miss any of them anymore--we're only talking about roughly 30 dates.
  4. Lead music four times at church. The small church we've started attending has one do-everything music director, who plans the service, plays guitar or piano, sings, and does a host of other things. When she heard that I play guitar, she was happy to hear that she might actually get a Sunday off once in a while. I did this once last year for Father's day. It didn't go well, but I suspect that doing it more often would help. But in order to do that, I'll have to...
  5. Practice guitar at least three times a week. No, I don't intend to become the next Zed Shaw. [Feel free to fill in your own snarky followup comment here. I'll wait till you're done.] But this is probably the absolute minimum to maintain a non-embarrassing level of competency in order to hit the above target.
Health

Yes, "health" is also personal, but these items fit with each other more closely than they do with the items in the "Personal" category:

  1. Keep all medical appointments. Here I'm talking about the regularly-scheduled, "periodic maintenance" things: eye exam, dental cleaning, "get these tests redone in 6 months" things. I have a habit of letting them slide so that "every 6 months" becomes "oh, right, I was supposed to do that last month, right? No? Last year?". Most places will let you schedule up to a year in advance now, so there's no good reason to fail this one.
  2. Give blood 4 times. I used to be pretty good about this, but I've fallen off the wagon. It's a little like the "regular maintenance" appointments--I know I can and should do it every 8 weeks, but 8 weeks stretches into 8 months. So I should be able to handle once every 13 weeks.
  3. Run at least 3 times per week. Indoor treadmill or outdoors, at least 2-3 miles. But realistically it needs to be more frequent and longer, because the next three targets are...
  4. Run two 5k races. I'm not sure why I've never run one, but there's no reason I can't find two free weekends out of 52.
  5. Run one 10k to 10-mile race. Again, I've never done one of these. The Pittsburgh Great Race is in September, and there are other similar races around. No reason not to.
  6. Run the Half. In 2009 I ran the Pittsburgh Half Marathon with my brother. Training up for it and running it were a phenomenal experience. I wanna do that again. In 2010 I got a horrible upper respiratory infection and couldn't run; this year I waited too long to register, and the half filled up. I've already registered for 2012, which means that now I can use the sunk cost fallacy in my favor for once!
So that's it, other than the "meta-target":

  1. Check in publically every month, without fail.

Keep me honest, Intarwebs.

Tuesday, September 06, 2011

Case Study: Python as Secret Weapon for C++ Windows Programming

One of my favorite features of Python is its interactive shell. If you want to try something, you type in the code and try it immediately. For someone whose first coding environment was the equally-immediate Applesoft Basic, this is just as natural. But if your introduction to programming was C, C++, or Java, the benefits might not be apparent, especially if you're trying to do exploratory coding in one of those languages.

So I'm going to walk through a recent experience as a case study.

The Problem

At work we develop a Windows program that talks to certain devices via serial cables. Those devices also come in wireless Bluetooth flavors, and we connect to them via a "virtual serial port". To the program running, it looks as if the Bluetooth device is plugged into a real serial port, because all of the wireless connectivity is abstracted away by Windows. These devices are unidirectional--they transmit data to the Windows program, which passively reads it.

If you power off and restart one of these wired devices, it will start chattering away at the Windows program with hardly a hiccup--our program never even sees a disconnect. However, we noticed that this didn't happen with the Bluetooth devices: powering down one of those requires the Windows app to reconnect. But the Windows app didn't even seem to get any notification that the device disconnected. So how do you solve this chicken and egg problem?


Research

It had been a while since I'd done any actual hardware serial programming, so I started with some documentation, and remembered that the RS-232 serial spec included a line called DCD, or Data Carrier Detect (also called RLSD, for Receive Line Signal Detect). Back in the dinosaur days, this signal meant that your modem was connected to the remote modem, and was able to start communicating back and forth.

Sure enough, a search brought up the right bit of Win32 API documentation, which told me how to detect an RLSD change on a physical serial port using the SetCommMask and WaitCommEvent calls. The question now became "does the Microsoft virtual serial port for Bluetooth support RLSD"?

Exploration

At this point I could have started up Visual Studio, created a scratch project, written a couple dozen lines of C++ code, compiled and linked, fixed the compile errors, compiled and linked again, run the program, fixed the inevitable errors that the compiler didn't catch, and then had my answer.

But I'm too impatient to wait for Visual Studio to start up, too lazy to write C++ when I don't have to, and I have the hubris to think I can come up with something better than the obvious solution. Programmers are funny like that.

So instead, I cranked up DreamPie.

Secret Weapon #1: DreamPie

DreamPie is, very simply, my favorite cross-platform interactive Python interpreter. It began life as a fork of Python's built-in IDLE command shell, and from there it's never looked back. It has excellent interactive completion for packages (so you can type "from sys import s" and get a list of "stdin, stdout, stderr").

Even better, it does completion when you're typing file paths in arbitrary strings. I use this a lot to get to modules I'm trying to test: "import os,sys; sys.path.append('c:/src/'" gives me a list of all the directories in in "c:/src".

It also has a slick separation of (typed) input and (generated) output, and a neat "copy only code" feature that makes it perfect for "try this code interactively, and when it works the way I want it, yank it into the actual source file" exploration.

DreamPie works pretty much the same on both Linux and Windows systems. It's reputed to work well on Mac systems, too, but I don't use them for day-to-day development.

So where I'd normally crank up the Python command interpreter for interactive exploratory coding, I usually reach for DreamPie instead.

But what I needed to explore now was the Windows API as called from C++, not Python.

Secret Weapon #2: ctypes

ctypes is a "foreign function interface" (FFI) that's been part of Python since version 2.5. An FFI is just a way to call code that isn't written in your current programming language. In our case, the functions I wanted to call in order to test out serial port notification are in the kernel32.dll library, which is part of Windows. ctypes makes this really easy. Well, easy if you happen to have the Windows API documentation and all of the correct C header files handy, and if you know exactly what you're looking for:

>>> import ctypes
... file_mode = 0x80000000 # GENERIC_READ from <winnt.h>
... open_existing = 3 # from <winbase.h>
... buffer = ctypes.create_string_buffer(100)
... bytes_read = ctypes.c_ulong(0)
... hfile = ctypes.windll.kernel32.CreateFileW(r'\\.\COM17', file_mode, 0, None, open_existing, 0, None)
... ctypes.windll.kernel32.ReadFile(hfile, buffer, 100, ctypes.byref(bytes_read), None)
... buffer.value
0: b'\r\n052100746029\r\n'
>>>

Hooray. We can call the Win32 API functions to open the serial port and read from it, just like we would from C code.

But... that's an awful lot of crap to remember and type. I had to know exactly the C code I wanted to write. I had to know the Windows API well enough to find the constants and the functions to call. I had to know the ctypes API well enough to wire up Python to the C return values via ctypes buffers.

What a chore. Did I mention I'm lazy?

ctypes is the universal adapter--it can connect Python code to anything. But if you're specifically looking to call the Windows API, there's an even better tool:

Secret Weapon #3: PyWin32

PyWin32 predates ctypes, but it has a similar goal: gluing Python to something else. In this case, something else is specifically the entire Win32 API. PyWin32 consists of about two dozen modules, for example, "win32print" for printing, or "win32gui" for window handling, which wrap a good portion of the Win32 API.

The documentation is rather Spartan, but if you know the Win32 API side, you can map those calls to the PyWin32 modules without too much pain. The 8-line, hard-to-remember ctypes example above turns into just four lines of simpler code using PyWin32:

>>> import win32file # for CreateFile
... import win32con # for constants
... hfile = win32file.CreateFileW(r'\\.\COM17',
... win32con.GENERIC_READ,
... 0,
... None,
... win32con.OPEN_EXISTING,
... 0,
... None)
... win32file.ReadFile(hfile, 50, None)
0: (0, b'\r\n052100746029\r\n')

The Final Secret Weapon

My actual exploratory DreamPie session to see if Window's virtual Bluetooth serial port supported RLSD looked like this:

>>> import win32api, win32file, win32con
>>> hfile = win32file.CreateFileW(r'\\.\COM17', win32con.GENERIC_READ | win32con.GENERIC_WRITE, 0, None, win32con.OPEN_EXISTING, 0, None)
>>> win32file.GetCommMask(hfile)
0: 0
>>> win32file.SetCommMask(hfile, win32con.EV_RLSD)
Traceback (most recent call last):
File "", line 1, in
win32file.SetCommMask(hfile, win32con.EV_RLSD)
AttributeError: 'module' object has no attribute 'EV_RLSD'
>>> win32file.SetCommMask(hfile, win32file.EV_RLSD)
>>> win32file.GetCommMask(hfile)
1: 32
>>> win32file.EV_RLSD
2: 32
>>> win32file.WaitCommEvent(hfile)
3: (0, 32)
>>>

This is an actual copy of the DreamPie buffer from my test session, mistakes and all. This is what really happened when I tried to figure out if RLSD would work:
  1. I typed up the code to open the serial port, which I knew should succeed, and it did.
  2. I looked up the Win32 API call to get the "event mask", or the set of events that were being watched on the serial port handle, and saw that it was "GetCommMask". I blindly typed "win32file.GetCo", and lo and behold, DreamPie brought up a list of completions, which assured me that GetCommMask was there.
  3. The Win32 API said that GetCommMask returned its result in a buffer passed into the call. Knowing that PyWin32 usually does a pretty good job of hiding return buffers, I decided to just try calling it with the input parameter, and got back zero. That made sense, if the serial port wasn't being monitored for events.
  4. So I decided to push my luck: if GetCommMask worked, SetCommMask should work, too. A quick peek at the documentation, and... hrm. win32con didn't contain the "EV_RLSD" constant I was looking for to monitor the RLSD signal.
  5. Well, I could have just typed the exact value (0x020) from the Windows docs... or I could just retype the line and use PyWin's autocompletion to see if win32file has the constant. I typed "win32file.EV_", and I had my answer. Then a quick re-test of GetCommMask() showed that the value was set.
  6. The API docs claimed that WaitCommEvent should wait for one of the masked events to occur, and then return which one occurred. But the documentation showed that it took another of those return buffers. Thinking that PyWin32 might help me here, too: I typed "win32file.WaitCommEvent(hfile)", and the call appeared to block.
  7. So I powered down the device, and within a few seconds, I was rewarded with the return value from WaitCommEvent: (0, 32). Aha. This meant that the Windows API version of WaitCommEvent returned 0 (for success), and that the return buffer contained 32, or EV_RLSD.

I included all the steps, including the mistakes, to show the last secret weapon: flexibility. Be willing to bounce back and forth between the documentation, the code you think should work, and the feedback you get both from the code under test and the tools you're using--and be willing to change your mental model based on that feedback.

In reality, this whole test took under five minutes from "Hmm... I wonder if I can use the DCD signal" to "Aha, looks like it works! Time to test it in C++." To be honest, I didn't even type out the whole ctypes version while testing--I started on it, realized that I'd have to look up and type all the constants by hand, then restarted DreamPie to jump over to PyWin32. Remembering that you can switch tools on the fly keeps you from getting stuck in ratholes that aren't directly related to the task at hand.

Flexibility is the key to fast and efficient exploratory coding. Using an interactive language like Python with a good set of support tools and libraries can be a secret weapon for speeding up exploratory coding--even when your target language is C++.


Wednesday, August 10, 2011

Stages of Competency

After doing "this programmer thing" for a few years now, I've noticed a pattern in how I acquire skills and techniques. It's surprisingly consistent, and consists of these stages:


0: Awareness

I've heard of the technique and can regurgitate a definition and a couple of use cases. I can probably pass a really bad phone screen (and in my experience, most of them are).

1: Familiarity

It's intrigued me enough that I've read up on it. I've probably looked at some code that uses it, and I can pick it out of a crowd, but I still mentally skip over it when reading its code (a bad habit that makes it harder to get past this phase).

At this point, if I were asked "what is X" or "how does X work" in an interview, I can probably pass the question, as long as there isn't a followup involving coding or something like "what are the pitfalls of using X over the long term", which is why I don't use questions like that in interviews anymore!

2: Functional Understanding

At this point I've either had to work with someone else's code that uses it, or else I've gone through an article that shows how to use it. I don't mentally skip over it anymore, and I can debug and modify it with some difficulty. Importantly, I can tell someone else what it's doing, but I will probably get embarrassed if I try to get into the details or (worse) debug it with them.

But I can't usefully synthesize anything with it. I get to a point in code and think, "Ah, this is a good place to use X!". Two hours (or more) later, I have bruises on my forehead from bashing it into the desk, I'm thinking "THIS CAN NOT BE THAT HARD", and I start wondering why I don't stay with the subset of techniques I know like the back of my hand. That's really tempting.

I get stuck in stage 2 a lot. I was there with C++ template metaprogramming for about five years, and I'm still there right now with Python metaclasses.

3: Understanding

After several frustrating episodes in stage 2, I do exactly the same thing in another context and... it makes sense. It works. I don't believe it, so I tweak things that should make it break, and it breaks in predictable ways. And I can reverse the tweaks and have it work again, predictably.

At this point, I always have the same three internal questions: a) do I really understand this? b) how did I not really understand this before? and c) what am I missing? I get uncomfortable not knowing how I know something.

Then all is well until I try to teach it to someone else, and we end up in another multi-hour WTF session.

What I've really learned at this stage is a single "groove" that works. As long as I don't deviate too much from the way I've used the technique, everything is fine. I think that subconsciously I know the limitations of that "groove", so I don't tend to make the little changes that expose the rough corners of my understanding. When I'm working with someone else, they have different edges to their own understanding. That's when I get this "uh-oh" feeling that tells me I really don't know what's going to happen when we do this.

Absent working with other people, I still think I understand it, which is a dangerous bit of self-delusion, and the biggest reason I'd rather work with a team than solo.

4: Competence

I don't know how I get here either, except maybe via repetitions of stage 3. In fact I don't usually notice even getting to this stage. The sign is usually that I'm having to do something outside the "groove" of my usual use of a technique, and that little "uh-oh" goes off, and then... it still works. Or else someone asks me about what would happen in a nasty corner case, and what comes out of my mouth is a better explanation of the details than I thought I could come up with.

This is also the point at which I finally feel comfortable writing about the technique, showing someone else how to use it, or trying to extend or modify it. The irony of it is that unless I do those things earlier, when I don't feel competent to do so, I tend not to get to this stage.



The funniest thing about this model is that if I look at code I've written in the past, I can usually pick out where I was on the scale when I wrote it. Again, I can't say exactly what the "tells" are, but when I get to stage 4 on something and look back at earlier code, I can think "ahh, ok, I was stuck in stage 2 at the time, and the places this code will break are probably X, Y, and Z."... and they usually are.

Forget owner's manuals--I wish brains came with source code. This progression would make a lot more sense then.

Wednesday, October 20, 2010

Switchpy

One of the consequences of the 2.x-to-3.x Python changeover is that I need to keep both versions around for a while on my Windows dev workstation.


Actually, strike that: I need to keep many versions around:
  • 2.5.4, because that's the earliest version we support at work for some internal tools
  • 2.6.6, because one particular internal tool jumped the gun and started using the "with" statement before we migrated to...
  • 2.7, because that's what we're migrating those internal tools to (slowly)
  • 3.1.2, because that's what we're targeting for new development
  • A "special" 3.1.2, which mimics the version we've modified for use in our embedded devices
  • The most recent 3.2 alpha, for testing
  • A 3.2 trunk install, for testing patches
Virtualenv doesn't exactly do what I want: you have to install it from within an already-installed version of Python, and it doesn't support Python 3 yet (although there is a fork that does). Plus it doesn't handle anything other than environment variables--it doesn't understand Windows' defaults.

Ned Batchelder wrote a neat script that does some of that, but again, it doesn't handle everything.

So starting from Ned's script, I came up with switchpy:
  • Supports Windows Python versions from 2.5 up to 3.2
  • Changes the local PATH environment in the current shell (via the same batchfile trick as mpath)
  • Updates the Registry-based associations (via code from Ned's script)
  • Pings Explorer so that if you run "python.exe" from the Start | Run command, it notices the update
  • Automatically reads installed official versions from the Registry, so you can say "switchpy 31" instead of "switchpy c:\python31"
So now, testing scripts in multiple versions of Python is as easy as:


C:\src\myscript>switchpy 25
Switching to Python at C:\Python25\...
Python is now C:\Python25\

C:\src\myscript>py.test
============================= test session starts =============================
python: platform win32 -- Python 2.5.4 -- pytest-1.3.0
test object 1: C:\src\myscript

myscript\tests\test_script.py ...

========================== 3 passed in 0.03 seconds ===========================

C:\src\myscript>switchpy 31
Switching to Python at C:\Python31\...
Python is now C:\Python31\

C:\src\myscript>py.test
============================= test session starts =============================
platform win32 -- Python 3.1.2 -- pytest-1.3.1
test object 1: C:\src\myscript

myscript\tests\test_script.py ...

========================== 3 passed in 0.03 seconds ===========================




For now, you can find switchpy in the same bitbucket repo as mpath; if I add any more scripts, I'll probably end up making it a more general repo.

Monday, May 10, 2010

Language of the Year

Several some many years ago, I decided to learn a new programming language a year. I don't subscribe to the "once you're a programmer, you can learn any new language in a few weeks" theory, so I didn't intend to get to guru status on any of them in 52 weekends and a few nights. Instead, the idea was to get good enough to be able to read code well, synthesize code at a decent level of competency, and understand the ideas behind the language, then move on.


If memory serves me correctly, I started with Java, then moved through REBOL, Python, Ruby, Scheme, and Lua before I dropped the practice. I tried getting back into it with Haskell a few years ago, but didn't take it to the "ok, I get it now" level of proficiency--mostly because of time constraints of changing jobs rather than any issue with the language.

I did learn something from each of these, which is the real point of the exercise. Now I'm thinking about starting a (belated) Language of the Year for 2010. The contenders are:

Erlang
Pro: interesting concurrency (independent processes) and data (immutable) models.
Con: might be too big to get my head around in nights and weekends

Io
Pro: Small surface area; looks like "Lua with a prototype-based object model" so far; might be useful practically.
Con: Not sure there's enough new there after doing Lua and REBOL.

Clojure
Pro: Interesting transactional memory model. Also, parentheses.
Con: I'm wondering how much time I'll spend re-learning the Java environment vs. learning Clojure.

Go
Pro: Interesting concurrency model; might be useful practically.
Con: Not sure there's enough different there, compared to Clojure or Erlang.

Part of the problem is picking a language that different enough to make it worth learning, but not so different that I can't use it for small, practical tools that I can actually use day-to-day. The Haskell and Scheme experiences showed me that if I can't use what I'm practicing day-to-day, I find it hard to keep devoting time to the project.

At the moment, it's looking like Clojure > Go > Erlang > Io. I'll probably pick up a distribution for each, get as far as "hello world", and then decide.