Recurse summer 2023 - part 2

I haven't posted much since starting at Recurse in July 2023 and extending my initial 6-week half-batch to a full 12 weeks. Here's a belated update on what I did in the second half!

Godot beeps via ChucK

First, I did experiment more with the "game" prototype that I started in my first half. One thing that I achieved was realtime control of an external sound engine, by sending OSC messages over local UDP.

The sound engine in question was written in ChucK, a very odd realtime program / little language for making synthesizers and audio processors. I had a good time playing with ChucK and seeing if I could make a nicer way to work with realtime polyphony supporting large numbers of note events. This deserves its own blog post; I wrote quite a lot but as of this writing have yet to make that readable.

// Small excerpt of ChucK code...

class PolyphonicAdsrSynth extends PolyphonicInstrumentBase {

    fun void play_one_note(NoteParams params, NoteOffEvent off_event) {

        dur attack;
        dur decay;
        float sustain;
        dur release;

        ADSR adsr => output;
        TriOsc osc1 => adsr;
        TriOsc osc2 => adsr;
        TriOsc subosc => adsr;

        Std.mtof( params.note ) => float freq;
        freq => osc1.freq;
        freq * 1.0055 => osc2.freq;
        freq * 0.498 => subosc.freq;

        0.5 => osc1.gain;
        0.5 => osc2.gain;
        0.6 => subosc.gain;

        Math.random2(2, 500)::ms => attack;
        Math.random2(2, 50)::ms => decay;
        Math.random2f(0.4, 1.0) => sustain;
        1000::ms => release;

        adsr.set( attack, decay, sustain, release);
        adsr.keyOn();

        off_event => now;
...

Eventually though, I found working with Godot too frustrating - I felt I was wasting my time wrestling with its very complex pointy-clicky interface more so than I was doing challenging programming, and I didn't think I'd be able to realize my user interface ideas within the time I had remaining. I wanted to stretch myself as a programmer, not become an expert (or even productive) Godot user. So I abandoned the project.

The prototype code is still up in my godot-bounce-1 repo. The code that converts "hit" events to OSC messages over UDP is here. Even being written in GDScript, it can handle thousands of simple note events per second.

Derailed by COVID, ugh

I lost some time to this. And depression. A lot was going on my personal life.

Jobs search prep; Algorithms study and practice

Sometime during my second half, I decided it was time to start getting my head in the game for job interviews. It's generally advised to put this off a bit when starting at recurse, so as to focus on work that's intrinsically motivating to you, but midway through is a good time to start thinking about it.

First, I started talks with Recurse's wonderful recruiting group.

I also joined a weekly "Careerist Crud accountability hour" group to work on "the boring parts" of job search. I hadn't updated my resume in several years, yikes. This was a good opportunity to reflect and dig through old journals as well as ruthlessly triage old cruft that no longer seems relevant.

Also joined another weekly group that met to discuss system design interviews, something I have some lingering PTSD about from a really bad experience (unfortunately, I ran across the hopefully rare interviewer who treats the process as purely adversarial and an opportunity to be relentlessly hostile).

And, I started reviewing algorithms fundamentals. Having never formally studied computer science - I'm largely a self-taught programmer - I again turned to Robert Sedgewick's very good book Algorithms, 4th Edition. I had read most of it several years ago, but forgotten a lot. (Like, what's a heap again?). Not surprisingly, it's been easier reading the second time around!

# Excerpt of heap code, `sink` and `swim`, from Sedgewick practice

    def swim(self, k):
        while k > 1 and (self.less(k // 2, k)):
            self.exch(k // 2, k)
            k = k // 2

    def sink(self, k):
        while 2 * k <= self.n:
            j = 2 * k
            if (j < self.n and self.less(j, j + 1)):
                j += 1
            if not self.less(k, j):
                break
            self.exch(k, j)
            k = j

I alternated reading Sedgewick with practicing some core concepts in relevant leetcode problems. This was a great "when in doubt, program" activity while I figured out what my last project might be. I'm not interested in the competitive aspect of Leetcode, and I think leetcode-style problems are really over-used for interviews at the expense of more practical hands-on programming challenges - but they are pretty ubiquitous. And somewhat to my surprise, I'm finding it a lot more fun this time around.

Crafting Interpreters

The most fun I had in my second half-batch was joining a Crafting Interpreters study group. This was inspired by a random pair programming session with a fellow Recurse attendee Moritz Neeb, who was part-way through Part Two: A Tree-Walk Interpreter and building his own Python implementation of the book's object-oriented language, Lox. This was new territory for me; I had never built an interpreter of any kind before, and barely knew what an AST might be.

Moritz was very patient and great at explaining what he'd built so far and the next step he was trying to implement. (I don't recall what.) You can work through the book by literally typing the Java examples given, or translating them directly to the language of your choice, but I recall that we decided that day to try to make the code do what the book was describing without following the example by rote. Challenging, and I think we kept failing tests, but very fun!

The study group had been at it for a couple weeks, and there were just over two weeks left in my batch. I was looking for a substantial project to do beyond Leetcode, and the pairing session was so inspiring that I challenged myself to try to catch up and finish Part II of the book alongside the group!

Some folks were using it as an opportunity to also practice a new language, but I thought given the time frame and the number of new concepts I would be learning that I had better stick with a familiar tool. So, here's my own Lox implementation in Python. I did use this project as an excuse to get up to speed with using Mypy for gradual type-checking in python 3, since 100% of my professional experience had been with legacy Python 2 codebases. Having spent significant time at my most recent job fighting with/against Sorbet in Ruby, I found Python type hints refreshingly straightforward.

I didn't know if two weeks would be enough time to finish all the chapters in part 2... - but I did it! I got inheritance working on my last day just after the Never Graduate celebration picnic!

// Some example Lox code, testing inheritance

class Foo {
  methodOnFoo() { print "foo"; }
  override() { print "foo"; }
}

class Bar < Foo {
  methodOnBar() { print "bar"; }
  override() { print "bar"; }
}

var bar = Bar();
bar.methodOnFoo(); // expect: foo
bar.methodOnBar(); // expect: bar
bar.override(); // expect: bar

Unfortunately that meant no in-batch time for part 3 - A Bytecode Virtual Machine ... but if I get bored in between job interviews maybe I'll give that a go!

Would I do it again?

Absolutely. I haven't been this inspired about "just programming" in a long time. My confidence took a bad knock from layoffs, but I feel better than ever. I hope to do another full retreat some day. For now, I've been dropping by the center regularly and hanging out on some Zulip channels, because as we say -- never graduate!