adventures in elisp

samer masterson

creating a daily habit

I have zero daily habits. There is nothing I do that is propelled forward by the sheer fact that I do it every day. Even brushing my teeth is a conscious effort which I have to manually remember to do.

I’ve read about the power of habit, though, and I’ve wanted to leverage that power to become more productive for a while now. Hacker School is awesome because it seems to be optimized for turning want-to-do’s into do’s, and that makes this summer the perfect time to try to build some new habits.

I’m going to start small, and try to create a morning ritual for myself, defined by the following components:

  • open emacs
  • read previous day’s diary entry
  • write down what I want to accomplish for the day
  • open geary, answer emails
  • open firefox, check zulip
  • close geary, all non-work and zulip tabs in firefox

According to wikipedia, habit formation can be broken into three steps: the cue, the behavior, and the reward.

  • As a cue, I have pasted a post-it note to the top of my computer which outlines the habit I’m trying to form. I will also have a note card with check boxes next to the steps of the ritual that I will leave inside of my laptop every day, so I will not forget to do the ritual when I open my computer in the morning. This is key, because my memory is terrible, and the reason I usually fail to follow plans is because I forget that I had a plan in the first place.
  • The behavior is defined by what I’ve written on my note card. As I do each step in order, I will mark it off on the card.
  • As for the reward, once I have checked off everything on the note card, I will look at it and be happy that I finished my morning ritual. I might augment this by delaying any caffeine intake until the morning ritual is finished.

My hope is that I can make an easy transition from the end state of my ritual to getting work done, because all non-work things will be taken care of, and ideally only emacs will be open. According to wikipedia, “Lally et al. (2010) found the average time for participants to reach the asymptote of automaticity was 66 days with a range of 18-254 days.” So I don’t expect the habit to take immediately, and I will be satisfied if I reach automaticity within 254 days. Though I hope it doesn’t take that long! :)

Also, I’m making progress with the parser. Keeping track of how many non-terminals I’ve implemented is trickier than I thought it would be, because I implement the easy branches of the non-terminal first, with the intent to fill in the gaps later. So I’m not as concerned with noting how many non-terminals I’ve done a day. Every logical chunk of work turns into a commit, so I might track commits instead.

doing hard things, an experiment

I thought I was scott-free after figuring out the design of the parser with Aki yesterday, but it turns out that I was just getting warmed up.

I looked over go’s grammar, which is thankfully available online as part of go’s spec… but the entire grammar has about 70 non-terminals! Most of those non-terminals will need a function to parse the associated tokens, a set of terminals to be checked to see if the upcoming tokens conform to the non-terminal’s spec, and a data type representing the non-terminal.

The design phase is finished, and so what I’ll be doing really amounts to a manual translation of the higher-level grammar into go code. It will require a large amount of focus, but not a lot of creative work.

I’ve never faced a task of this magnitude, so I’m going to try to set some rules/boundaries around how I spend my time:

  1. Implement 5 non-terminals a day: I figure the most reliable way to make progress is to get small chunks done every day. Five is a fair amount, but it isn’t dauntingly large, and it means I’ll be finished in about two weeks. I will probably need to adjust this number as I build the parser.

  2. Follow test-driven development: I’ve viewed test-driven development with suspicion in the past because of the number of people on the internet who sing its praises. Max has experience with it, though, and told me that TDD makes it easy to track your progress, gives you confidence in your code, and rewards you with passing tests as you write more code. All of those are useful things that will make it easier to maintain some momentum. I also like the fact that my first step will be writing a test, which is a small, tightly defined task, especially in contrast to implementing all the things involved in parsing a non-terminal. I’ve implemented the test for what I want to do next, and it was fun to think about what I wanted my program’s output to be before I had written a line of code. It’s not something I’m used to, and I feel like it was helpful. I’m afraid that having tests will make refactoring harder, but it will also give me confidence that a refactor didn’t break anything, which is a trade-off I’m willing to make.

  3. Mark my progress: I will mark my progress in the grammar, and note how many non-terminals I finished in my diary. Then, on days where I get less done, I’ll be able to look over how much I’ve already finished and be happy.

  4. Drink caffeinated drinks: Caffeine is my favorite drug. I have trouble focusing on things without caffeine, so I will allow myself to have as much caffeine as I want. I can see no way this can go wrong.

  5. Listen to music: Listening to music is a double-edged sword. For most of my life, I never listened to music while working because it would drown out my inner voice, which makes it harder to reason about things. However, I recently learned that listening to music makes it easier to stay on task when I’m working on less interesting things, because my inner voice is usually bouncing off the walls in my skull, distracting me with irrelevant thoughts and bad jokes. I have experienced bouts of hyper focus with the combination of music and caffeine when working on mechanical projects that require a certain level of concentration. Hopefully I’ll be able to take advantage of that on this project. Morrissey, give me strength.

  6. Allow myself to be distracted: As long as I’m on track to finish five non-terminals, I won’t allow myself to feel guilty for pairing with people on things that are completely unrelated.

I have my tests written for the non-terminal I’m implementing next. Here goes nothing!

hacker school day six

Today was fun. Finally nailed down the design of the recursive descent parser with Aki’s help. Aki told me that the usual way of determining which branch in the grammar to take was by finding the first string literal unique to the branch. That idea was the catalyst I needed in order to finish the design of my parser, and I got the parser to work with package clauses and import declarations.

I had been stuck in the design phase of the parser for the last three days, and it feels good to finally be unstuck! I can’t wait to make more progress tomorrow.

significant conversations

Everyone here is incredibly fun to talk to, but I want to talk about the significant conversations with alumni and facilitators I’ve had that have influenced my “plan” for hacker school, and have significantly influenced my progress. For reference, I came in knowing I wanted to write a go compiler in go, because compilers are black magic to me, and I’ve become enamored with go over the last two months. In more-or-less chronological order:

  • David Branner: we met during the pre-hacker school dinner. He thought that having an overarching project that you contributed to “by default” for the batch was a good idea, but you should let yourself get “distracted” and work with your peers on unrelated, interesting things. This was already kind of what I was thinking of doing, and it was nice to get a little validation from an alumnus.

  • Aki, my hacker buddy: sent me a blog post by Sasha Laundy, an alumna, on how to get the most out of hacker school. What stuck with me the most was the advice that one can be done with a project before a project is “done” (and if you’re thinking, is it more correct to say “done” or “finished”, this is an interesting blog post on the subject), and if you’re past the intense learning stage of a project, it’s okay to drop it and move onto something else. Aki told me that he got too wrapped up in a project he was trying to finish during his batch, and that he should have dropped it sooner. So I thought it was surprising when he said he liked David’s idea of having a default project that one returned to after getting “sidetracked” by other interesting things.

  • Tom Ballinger: after letting me know that it’s totally fine to do just a bunch of small projects with no plan in mind, he told me that he noticed that people who want to work on compilers often start by writing an interpreter.

With Tom’s observation in mind, I worked on a lisp interpreter in go for the next two days. Wish Sasha’s advice in mind, I stopped working on the interpreter after I had learned my fill: I was more interested in how interpreters worked than completing the semantics of my baby lisp language.

  • Jordan Orelli: did a code review of my interpreter, and got me thinking about solving problems using channels. Showed me a video on lexing using channels in go, which was incredibly eye-opening.

  • Aki, again: we met for coffee and talked about the road to a fulfilling job without getting a PhD, and the trade-offs involved. We had also talked about continuing to work on side projects after leaving hacker school. With those things in mind, I’m thinking about changing my plan to be submitting a patch to a free (as in freedom) compiler. Building a go compiler in go is definitely on the path there, but it’s more okay to put it aside once I’ve learned enough if the goal is contribute to a compilers project, instead of the goal being the compiler itself.

And, of course, all of the interactions I’ve had with my batch mates, the facilitators, and the other alumni have been incredibly rewarding. I still can’t believe how important one’s environment is to doing good work, and it’s affected me more than I thought it would. I’m not used to being around so many different people I can geek out with.

I set the time to a minute before midnight, because I want this to count as my blog post for Friday :)

semicolon drama

note: this is kind of a crappy blog post, most of which I wrote this morning, because I didn’t get any work done after like 4pm, and I have a much clearer (though still not clear enough :) ) vision of how I want to my abstract syntax tree to look, and how I’m going to pass nodes to it (INTERFACES AND CHANNELS YO).

It’s interesting having the history of a programming language available on the internet. I was reading about the post where go changed from semicolon-required-expect-for-these-special-cases, to lexical semicolon insertion. There seemed to be a significant number of people with complaints, who wanted to write in weird styles, or who thought that the lack of semicolons would mess with things. It’s interesting to see so much hate for a feature that cleans up the syntax so much with no loss of clarity: if you need them, the semicolons are there for you!

I learned a nice lesson about the importance of compilers when participating in programming language discussions: rob (who announced the rule) specifically stated that the rule was a lexical one, not a semantic rule (he offered javascript’s semicolon insertion rule, which was semantic and totally wonky, in contrast). He stated that this was because having the rule live in the lexing stage would be more clear than having it in the parsing stage. The fact that it’s a lexing rule means that it’s hard and fast: lexers shouldn’t do special processing. And it was surprising how many people proposed that the special form

a := (
      a,
      b,
      c
     )

not be covered by the rule, and be turned into

a := (
      a,
      b,
      c
     );

instead of

a := (
      a,
      b,
      c;
     );

This is only possible if the lexer is counting parentheses, and so what people were really asking for was another special case in the parser to handle things. And as someone writing a go parser, I appreciate rob & the gang for striving to keep the language as simple to implement as they could :)

hacker school: day three

Today was fun! My lexer is pretty far along: it lexes itself :D. There are a couple corner cases I need to handle for semicolon insertion. Lexing is more straight forward than I imagined. My only prior exposure to the dark art of lexing was writing out the syntax for a Cool language lexer to be fed into Flex. I actually brought food today (OATS, yay :D), so my energy levels were higher today than they were yesterday. I felt a dip in energy come 5 pm, though. I powered through til 5:40 working on the lexer, when I had to stop and take a break. I started working on my side-side project, a standard library in C, which is different enough that I thought it would help me get on track, or at least waste time productively. I think it worked a little, but Dana suggested we get soy chicken stuff (which came first, the soy chicken or the soy egg?) from Whole Foods. The walk was nice, Dana’s super cool, the soy chicken was 7 bucks so we didn’t buy any and got bagels and strawberries instead (my first NY bagel tasted suspiciously like the bagels back home…), spent time blabbing (blab blab blab), walked back, and I felt reinvigorated and finished up the lexer.

The room was nice and quiet when I got back, too :) Music helps me focus on tasks, but it impedes making design decisions because it stifles my thoughts. When there’s noise, I need to play music or else my focus gets drowned out in the sounds that are happening around me.

Overall, today was rewarding. I had underestimated the impact of being around such awesome people.

proper way to return distinct types (in go)?

Ran into a funny problem today with the parser. My parser would only handle s-expressions (like (these :D)), and I needed it to evaluate bare symbols as well. So if you ran, “(def meow 333)” (333 is the number of the half-devil, btw), and then “meow”, I wanted my interpreter to return “333”. My issue was that my parse function only returned Lists, and those are fed into eval. I couldn’t just stuff the symbol (in the example above, the “meow” :3) into a list, because my eval function evaluates lists as function calls. I didn’t want to return an interface type (void type for those familiar with C) because that seemed to obscure the purpose of my Parse: it should only return two things, a list or an atom.

My solution was to return multiple values (one reason why go is awesome) from the parse function, a list and an atom. When my parse bumps into a bare symbol, it returns nil as the list, and when it finds a list it returns nil for the atom. Czech the (simplified, sans-error-checking) code:

func Parse(s string) (*list.List, *Atom) {
        strs := tokenize(s)
        t, strs := pop(strs)
        if t != "(" {
                // handle bare symbols
                return nil, &Atom{Value: t, Type: "symbol"}
        }
        ast := gen_ast(strs)
        return ast, nil
}

And then in my main loop, I evaluate whichever of the two that is not nil:

for {
        // blah code hidden
        input = getInputFromUser()
        ast, sym := parse.Parse(input)
        var a *parse.Atom
        if ast != nil {
                a = eval(ast)
        } else {
                // sym != nil
                a = eval(sym)
        }
        fmt.Println(a.Value)
}

But now that I think about it, it might be cleaner to just grab an interface bare and pass it in. Though it feels slightly wrong :O)

LAMBDAS WORK

I GOT LAMBDAS TO WORK! It was a stupid problem in my def code for hooking up symbols to the environment. I assumed that every symbol evaluated to itself (because I finished it before I had functions), and so every function was simply returning its own memory address. That bug took so long to find, I should have figured it was a dumb one.

hacker school: day one

I might blog about how Hacker School opens up later, though it went kind of as expected. I was super scared about not being able to actually get to work, but Tom Ballinfdjskjfdwords turned me on to the idea of making my first steps towards a compiler by writing an interpreter… I thought it would be hilarious to write a python interpreter in go, and so after my first morning check in, I started to look at interpreter building resources. I figured I should work on the easiest possible interpreter to finish, in order to build some sort of momentum, and I found Peter Norvig’s blog post on how to write a lisp interpreter in python

At least the parsing part seemed to map to what I could accomplish in go, so I decided to give it a shot. I had a bout of hyper focus (from hacker school or the coffee? we’ll see when my tolerance builds back up) that carried me from nothing to building an abstract syntax tree in two hours. Hacker school is surprisingly noisy, so I shut myself in a room (with a friendly note on the door letting people know they were welcome to come in!) and turned up my music. Caffeine + music makes focusing easy. I can’t help but sing, I hope nobody outside heard me! :)

Afterwards, I took a break with Max and walked to a Starbucks with him… I was surprised when I learned he had been working on the exact same project, using Norvig’s materials too! So we banded together in the room. At first we worked in silence, occasionally asking questions of each other. I got stuck real bad, and I proposed “pear programming” (it’s a silly name, isn’t it? especially b/c there are no pears involved.) We spent the next couple hours hashing the problem out verbally during the sticky parts, while I wrote the code. I was shocked at how fulfilling and effective pair programming was! It was nice to slow down and think hard about the program architecture before writing any code. And every victory was shared, which made working through the difficult parts even more rewarding. We ended up finishing eval for atoms and ‘def’ before our collective minds turned to mush. Overall, it was a rewarding experience.

However, I am unconvinced of pair programming’s utility when used for smaller, incremental changes. In pair programming, the fun (and productive) part is talking about design, not finding typos.

I stayed late today, thinking I would finish a lot more. I got a little bit farther with lambdas, but they aren’t working yet, and I’m going to save my debugging session for tomorrow.