A Pleasant Work Reflection in May.
I last wrote about some frustrations I encountered with a new project and database at work. Towards the end of my post, I remarked on using an ML inspired language like OCaml for extra compile time guarantees. I couldn’t be happier to say that since February, I have been working almost exclusively in Go and F#.
This post may be informational, but mostly serves as a way for me to express gratitude for languages and tools I’ve been using recently.
I might have pushed more for OCaml, but leaving .NET entirely wouldn’t have been pragmatic at the time. Since our switch to developing new .NET code exclusively in F#, I’ve experienced everything I hoped to from a language in the ML family tree. Countless bugs have been prevented from some light work up front, designing types that adhere to business rules. We have also removed over a dozen real bugs that exist (thankfully yet un-triggered) within production code.
This was all accomplished with less than half the effort to create the original implementation in C#.
Is it simple, is it safe?
Simplicity has been a major driving force in my Computer Science work. The things I’ve learned from the Go community are still impactful in the code I write in F#. Though, F# is certainly not as simple of a language as Go. F# has many additional features, many of which have no analog in traditional “Enterprise Languages”. For this reason, some developers have experienced a bit of a steep learning curve.
Features of OCaml and F# are intended not to make code as readable as possible, but to make it as correct as possible at runtime. I come to this conclusion a bit heuristically, since ML was written to develop proof tactics in LCF. LCF, being a mathematical theorem prover, you can conclude its features promote a language in which compiled code is as close as possible to correct code.
I’ve certainly seen that payoff internally within a few months. A very skilled senior developer could write a few F# signature files, and pass off the implementation to a Junior Developer. The first draft is often more reliable than would be possible with other languages.
It should be said that despite these promised effects on paper, the F# tooling I’ve come across feels less polished than the Go equivalents.
For example, I’ve had daily issues with my F# language server crashing.
That experience has improved over time, but I often wonder why a language focused on accuracy doesn’t yet compete with standard tooling developed in Go.
Examples of tools that I feel represent Go well include
I realize that Microsoft and other .NET developers heavily favor IDEs, C#, and other existing Microsoft tooling. For that reason, I tend to think there are just not yet enough F# developers working on Unix friendly tools.
We’ve more seriously adopted code generation practices in the last few months as well. Specifically, we’ve been generating API server and client boilerplate from swagger specifications. The time saved in early development as specifications changed frequently has not been measured, but could be estimated as 3+ hours saved weekly.
I’m much more fond of existing, deterministic code generation methods that use ASTs rather than new and computationally expensive language models like ChatGPT. These older methods have proved more reliable, and behave more like pure functions, where the same input will always give you the same output. You can put these tools into build pipelines and sleep easy at night, knowing their output will not change right out from under you.
The point I want to drive home here is that compilers and interpreters have been achieving code generation for decades. I’m surprised that there is so much buzz around code generation through language models, when we have had dozens of ways to generate code reliably that have been ignored for years. I encourage anyone with a development job to heavily lean into the power of these code generation tools. Many have permissive licenses, you can install them on your machines, they run quickly, and most importantly, you are the one in the driver’s seat.
What has been gained?
Overall, we’ve been making fewer mistakes on the basics, and this has allowed us to spend time producing better features. Refactoring, optimizing, and redesigning types all feel much safer. As more of these systems go into production, I’d like to return and share how these practices change the experience of the end user. After all, the final product matters a great deal.