2020-08-14
This week I completed my first week at Recurse Center. Recurse Center is (in their own words): "[an] educational retreat for anyone who wants to get dramatically better at programming". After my first week I think I can safely say that it is a lot more than that too.
I'm attending a Recurse Center batch because I want to slow down and learn things. As a professional programmer you are rarely afforded the opportunity to stop and ask: "Why does write
return the number of bytes written?" or "how exactly does a linker work?" At work it's usually sufficient to learn just enough to get by and move on to the next thing. After all: there's always more work to be done and not enough programmers to do it.
I'm writing about my time at Recurse for two reasons. First, I want to better retain all of the awesome things I'm learning while I'm here. In my experience, repeating what you've learned in writing is a great way to do that. Second, I want to become a better writer. I've always enjoyed writing, about technical topics or otherwise, but is also something I haven't been able to give a lot of attention to recently.
In service of slowing down and learning things I have a couple different projects I'd like to try working on. What they all share is in order to do them there is a lot I would need to learn. In order of concreteness:
Start build tool
Within build tool run a command
The build tool uses
strace
/something to see what files it openedShow the user the files that were opened, let them accept, decline, or edit them
Commit those as the new dependencies
Next time you use the build tool to run that command, it will only allow the command to read the files that were recorded as deps. This could be achieved with containers
This makes it easier to add new dependencies (just re-run the command in record mode) but also it could make it possible to catch when a command starts requiring fewer dependencies. Just sample the command in record mode and compare against the list of known good dependencies
In order to do these things I'm (re)learning C++ (needed for the Gamecube emulator, also needed to really understand why Rust is the way it is which is what I would use for projects 2 and 3) and Haskell (I want to understand type theory better).
With that, let's dive in. I'm breaking this out in to the buckets where I spent most of my time this week. Some of these sections will be more fleshed out than others, sorry!
I wanted to try doing most of my development on Windows at RC for three reasons:
I didn't end up using Visual Studio as much as I would have liked this week, for reasons that I'll get to. I did run in to one problem which is worth noting though. I had previously installed Visual Studio Community Edition to try doing C# web development a while back. This week when I started it up it prompted me for a license. I was a bit discouraged because not having a job at the moment meant I wasn't too keen to pay for a Visual Studio license. It turns out if you sign in with any Microsoft account you can continue use Visual Code no problem, at least for non-commercial stuff.
Hope to be able to talk more about Visual Studio in the future!
Once I decided that I would need to refresh myself a bit on C++ before diving in to the Gamecube emulator codebase I found developing in Windows was slowing me down, so I switched to compiling and running my programs in the Windows Subsystem for Linux (WSL). It uses virtualization, but is super fast. I'd love to learn how this works and it seems like this talk might be a good place to start.
I've been really impressed with how well it works, especially when paired with Visual Studio Code. The new remote extension makes the experience seamless. If I open a project that's in WSL VSCode knows to:
Most of the time I forget that Windows is involved at all. Most of the time. Also from some chatter on Twitter I'm seeing that there might be more monsters hidden here. If I run in to them I'll add them here. :)
On a whim I started using Microsoft OneNote to take notes this week. This thing is seriously great. Maybe I'll expand on this in a later post but, if you're reading this and you have a Windows PC I think you have OneNote. Give it a shot, it's everything I've wanted in a note taking application.
I learned C++ basics in college, and have used it in anger a few times since then, mainly when I was working on HHVM. So I've always felt like I knew the basic syntax and understood pointers. I learned a lot about how to debug CMake (that felt like what I spent most of my time on with HHVM) but I don't know a lot of the in between bits. I'm mostly interested in experiencing, viscerally, what Rust and Go feel that they are fixing about C++ so I can understand the trade-offs they are making better. So, I started from the beginning, working my way through the C++ programming language by Stroustrup and building a simple CLI application.
I realized that I had a misconception about C++ memory management. I thought that memory management in C++ was entirely manual, meaning that you had to free
everything yourself. It's a bit more sophisticated than that. I think the rules are this:
free
'd when it leaves scope.new
, which returns a pointerWhat's a smart pointer? There are several types of smart pointers in C++:
unique_ptr
: Allows exactly one owner of the underlying pointer.shared_ptr
: Reference-counted pointer.weak_ptr
: Not really sure what this one is for yet. I don't think you're supposed to use it unless you really know what you're doing.If you use a smart pointer as a member of a class, that will automatically be cleaned up when instances of that class are no longer in scope as well.
There are, by my count, at least 6 different ways to initialize things in C++:
std::string s{}
std::string s("hello")
std::string s = "hello"
(this is the one I was used to)std::string s{'a', 'b', 'c'}
char a[3] = {'a', 'b'}
char& c = a[0];
I'm not sure what all these are for yet, I need to read up on them more!
I spent a lot of time trying to use the new filesystems library that was added to the C++ standard library recently. I say recently because ... I can't exactly pin down when it was added. Some documentation I've found says it was added in gcc 8, but I had to use gcc 9 to get it to work. Maybe this was related to how that library moved out of the experimental section of the standard library.
A lot of stuff has changed in the language since I learned it 10 years ago. I think the only two features I've used so far are lambdas and the filesystem library but I'm looking forward to seeing what else has changed.
I had a great coffee chat with someone where we talked about build systems and how hard they are to debug. We posited that this might be due in part to their declarative nature. Most built tools allow you to declare targets and their dependencies. You don't say "In order to build my app, build this binary, then build this other thing". You simply describe the dependencies.
This gets really hard to debug. If you build a target, and you expect another target to be updated but it isn't, what do you do? Unlike an imperative system you can't stick a printf
in between two "steps" in your build process and inspect the state. This is a problem inherent in declarative systems.
One declarative language that does this well in my experience is CSS. Chrome and Firefox dev tools give you fantastic visualizations for how your CSS rules are affecting the layout in the browser. I think build tools should make visualizations a first-class part of the experience. Encourage people to generate graphs of their dependencies, of graphs of a given run, rather than hiding it in third party integrations.
I've tried to learn Haskell two times in the past.. Both times I gave up pretty quickly. I think I was frustrated by my inability to do simple things so much that I never got to the interesting parts. I think this time might be different.
The previous two times were in college and at a previous job. In both cases I was less experienced and more time constrained than I am now at Recurse Center. I've already asked a couple beginner Haskell questions at RC and gotten very helpful answers, so I'm feeling supported too!
I've also recently gotten the opportunity to learn two things from scratch: piano and skiing. In both cases I got to experience being really, truly bad at something. I was frustrated with myself. In both cases being frustrated didn't help, but welcoming failure and being comfortable with it did. Hopefully I can carry that experience over to Haskell as well.
Until next week: learn slowly and be comfortable with failure!