Until last week, I had never made a contribution to an open source project. Especially since this is a first that’s happened almost 10 years into my career as a programmer, it’s gotten me to reflect on how it came together and what skills went into it.
The contribution itself was to the Unison codebase manager, disambiguating two particular error cases and providing distinct error messages.
I wrote this error message
For the past few months I’ve had the personal goal to dive into functional programming technologies and communities. Unison is a functional language, heavily derived in design off of Haskell, and written itself in Haskell. Contributing to an alpha functional language by writing in a staple functional language was a perfect exercise for me.
The pull request itself is fairly modest. It adds a couple new data types, changes the signatures of a few functions, provides the copy for a new error message, and ties a bow on it all with a new integration test. But it represents, at least for me, the culmination of a significant amount of time, effort, and education. Here are my reflections on the factors that went into making the contribution.
Unison is a programming language / environment, so it was pretty important that I had spent a lot of time playing around with it. I had built a small 2048 clone, which I talk about in this other post.
In building that, plus messing around with a few other projects, I had expectedly come across some of the rougher edges of Unison (it is in alpha, after all). The first significant step on the way to becoming a contributor, for me, was creating a few issues on their Github page (one, two, three, four, five). The biggest challenge I had to get over here was my inherent cautiousness about bothering the maintainers. I really wanted to do my due diligence so as not to add noise or burden to their lives. Filing these issues and getting welcoming responses got me over that smaller bump, and further led me to think: if I could confidently point out flaws, what’s to say I couldn’t reasonably make improvements from a design perspective?
Also, importantly, I had spent a good amount of time in their Slack channel. Seeing people’s questions and comparisons to other projects over the past few months was helpful to understanding the project. I had also spent a decent amount of time discussing it with friends and making similar comparisons, so I felt confident I there wasn’t some significant gap in my grasp of the surface area of the project’s interface.
I’ve certainly found that a lot of my object-oriented experience does translate - fundamentally they’re both ways of making the computer do things. Some skills and knowledge pretty directly transfer, like abstract visualization skills, reasoning about data structures, and working with static types, and some skills and knowledge are helpful as reference points to contrast against, like considering the differences between Java’s inheritance and Haskell’s typeclasses. However, there’s a lot of new territory in functional programming, both from a technological and cultural standpoint. I’m finding it’s taking a lot of sleeves-rolled-up time to gain a working familiarity with it, which this book emphasizes and, so far, delivers.
Fortunately the book also introduces the reader to standard Haskell build processes (largely via Stack), and that helped me navigate Unison’s employment of Stack to build changes, run tests, and run my development version of the executable. The Unison maintainers have also provided a file called development.markdown which was hugely helpful for bringing me into the codebase.
That all being said, I wasn’t very familiar with a lot that’s going on in the codebase. Not only was I relatively new to Haskell, but it also took a while of staring at different files and pondering the directory structure to get a working handle on the internal architecture. I’d say my contribution was just about at the extent of my capabilities given my relative unfamiliarity.
One theme of confusion I had was around wondering what’s idiomatic in the FP culture, in Haskell, and in the Unison codebase. Should my new data type have been a 3-element sum type or represented as an Either with a new 2-element sum type for the Left case? Where do I even put new data type declarations? I fought through the discouragement of not knowing those answers and hoped that it wouldn’t be too bothersome to the maintainers for me to put up a solid guess and for them to give some feedback. Happily, it seems like I wasn’t too far off with the guess I put up.
I’m not sure I would have had the ability or, as importantly, confidence to make this contribution without already having a good number of years’ worth of general experience programming, debugging, communicating about code, and collaborating on a large-scale project via Github.
I felt very comfortable identifying and emulating their collaboration workflow on Github. I wanted to be very cautious about bothering the maintainers (which was the hill to climb in even putting up the issues I mentioned), so I first created a fork and put up a development PR that I messed around with over there. Nobody else ever saw that PR, but it was a good way for me to iterate on the PR, including the description / comments. I put a lot of importance in reading my own work - reviewing the PR diff and description like I didn’t write it. If I missed dumb stuff, or something was confusing, that would take up the maintainers’ time, and I wanted to minimize the cost impact as much as possible.
Another challenge my experience helped me with - I actually ran into another bug, believe it or not, while working through this contribution. As you might expect this was super confusing. By this point I had looked through the codebase and poked around at making changes for a few hours, and in every case something unexpected happened the most productive bet to make was that I had done something wrong, or misunderstood something. A lot of general experience debugging helped here - what do we know we know? Perhaps we should test with a value we can safely assume would work? Perhaps we should check out the main branch and test there? Perhaps we should see if other people have run into this? Thankfully this last thought led me to an open issue for the bug, confirming that I wasn’t crazy.
As always, the importance of good people and interactions in software development can’t be understated. And, as usual, the human factor has a profound impact on productivity, motivation and goodwill.
I’m very appreciative that I have friends who are not only passionate and knowledgeable about software, but very generous with their time. Specific shoutout to my friend Ian Jeffries who has been a singularly helpful mentor for me as I’ve taken my first steps into Haskell. A few essential answers from Ian about the Unison codebase architecture and build processes did wonders to release the pressure my head’s contact with the wall was making.
This particular project also benefits from having very inclusive and helpful maintainers. As is evident from their interactions on Slack and Github they really take their time to engage with the community, answer questions, and encourage people to get involved. I was fortunate enough to be able to go through the PR on a video call, even, with one of the cofounders Arya Irani, a cost-impact I never would have expected from him. I found that the personal touch gave a huge boost to my engagement, motivation and goodwill for future contributions.
Another important factor here is that I’m fortunate (and so grateful) to be in a period in my life where I’m happily working part-time. I have a lot of free time and energy to improve my skills and build things, which isn’t a luxury most people have at any given period in their life. I certainly can’t stay in this period indefinitely, which is why I’m very motivated to make the most of it.
This is to say that anything worth doing takes both time and energy, which are often the scarcest resources we have. In this case I didn’t have to stay up nights and weekends or dig deep for the energy to chase my personal goals. I can fairly confidently say that if I had a full-time job that I was pouring myself into, it would have been unlikely that this contribution came together.
On top of all these building blocks, I feel very fulfilled in my first OSS contribution. The PR was merged on that video call, and the four of us present in the meetup cracked open beer / kombucha to celebrate the occasion. It was a very nice moment. Again, the personal touches generate motivation to continue contributing.
This post isn’t all so much meant to simply celebrate a proud first for me. It’s partly to acknowledge and appreciate the privilege I enjoy. It’s partly to provide a personal story that could help demystify something that might seem opaque to people. And it’s partly to reflect on that piece of advice I hear around for people learning programming: a great way to learn and grow is to get involved with and contribute to an open source project.
Breaking down everything that went into this contribution has made me question that piece of advice. Take away even one of the factors I mentioned and making the contribution would have been a lot harder (surely much more frustrating). Unison is probably one of the more complex OSS projects I could have chosen for my first contribution, sure, but I still believe I’d have to rely on a lot of the same building blocks going into a simpler project.
I’d like to propose an alternative to (that partly agrees with) that piece of advice: seek mentorship, wherever you can find it. Find people with more experience, skills, knowledge and perspective than you and try to learn as much as you can from them. Mentorship is probably the best way to overcome nearly every challenge I’ve mentioned here. If you can soak up other people’s familiarities with some project’s interface, implementation, and development processes, you’ll be able to skip a lot of the discouraging (and sometimes defeating) trials.
Mentorship is especially helpful with respect to an especially discouraging problem one has to deal with when diving into something new: unknown unknowns. An answer from a mentor isn’t just turning a known unknown into something known, it’s potentially batting away dozens of unproductive avenues of exploration. For a small example here, I had run into a lot of cases where string concatenation in the Unison codebase was done with
<> instead of
++. I was starting to worry - does this represent a significant gap in my understanding? What’s the difference in behaviors? Ian provided the experienced answer that there is no difference in behaviors, Haskell folks just tend to use the more general form of some function if they can to maintain extensibility. A small touch of mentorship batted away what could have been many confused Stack Overflow searches.
I’ve talked a lot about wanting to minimize the cost impact on the maintainers here, which was probably the foremost thought in my mind as I went through this. That’s a worry that can be largely offset by structural motivation for mentorship like you might have starting out at a new job or within academia. The experienced people (professors, TAs, or senior developers) have organizational incentives or mandates to mentor the less experienced people (students or junior developers). Those two places are certainly where my most productive periods of mentorship happened, and I’m constantly reflecting on how much I’ve benefited from the mentorship I’ve received.
There might be less of this structural motivation in OSS, but there is indeed a chance you can find mentorship in an open source community. If you can, then yes, absolutely, get involved and try to contribute. Mentorship can come in many forms, big and small, including PR feedback or responses on Slack. The key is to benefit from other people’s experience as directly as possible. If the maintainers of the project or other active people in the community take time to teach you something, you’ve found something great.
Thank you for reading!