Last year, Exercism put together the #12in23 challenge. The goal was to learn a new programming language each month throughout the year. I was one of 135 people who completed the challenge, and I learned a lot along the way!
TL;DR
Exercism is an awesome platform, but the quality of exercises and language tracks is hit or miss. I solved all the exercises locally, and got a feel for the language tooling and installation. I looked at Clojure, WebAssembly, Elm, C++, Julia, OCaml, Pharo, Raku, Lua, Gleam, Crystal, and Rust. All of the languages presented me with new perspectives, but I don't think I'll revisit Pharo (Smalltalk) or Raku (Perl 6) anytime soon.
- My Goals
- The Exercism Platform
- The Languages (Clojure, OCaml, Rust, Julia, Pharo (Smalltalk), C++, Raku, Crystal, WebAssembly, Elm, Lua, Gleam)
- Follow the grain
- Github Copilot
- Conclusion
My Goals
When I first heard about the #12in23 challenge, I ambitiously decided to learn 12 languages that were as far apart from each other in semantics and paradigm as possible. Additionally, I wanted to learn the language well enough to actually build something that was non-trivial and production-ready, so that I could get a good feel of the entire lifecycle for the language. My proposed list at the start of the year was Clojure, TypeScript, OCaml, Elm, Rust, PureScript, Crystal, F#, Go, Julia, Lua, and Nim.
In February, themed months were introduced starting with Functional February. So I ended up switching up my list to fit the themed months. In the end, I swapped TypeScript, PureScript, F#, Go, and Nim for Pharo, Raku, C++, Gleam, and WebAssembly. Unfortunately, I missed the memo that you had to publish your exercises, so I missed the badges for March and April, but I got the rest of them.
I started the year off strong, completing 33% of the Clojure track. But the rest of the year, I mostly just did the bare minimum to get the badges. I didn't build any non-trivial applications in the languages I tried out. I did solve a good amount of Advent of Code in Crystal, though, so I guess that counts as non-trivial.
The Exercism Platform
Just to get it out of the way, I'm not a huge fan of the name... But the people behind the platform (namely Jeremy and Erik) love programming languages and have put together a really neat platform that works very well for the most part.
I used the Exercism CLI to solve and submit all the exercises locally. Many of the exercises and track syllabi are assembled by open source contributors, so there is a lot of variation in quality. Some exercises include a section to teach a new programming concept, then give step by step instructions for what is expected in the solution. Others describe the problem in a couple sentences, and you have to dig through the test file to reverse engineer what the solution should look like. Eventually, I just got in the habit of doing test-driven development, but it was quite a shock to go from nicely laid out instructions to another that gave you next to nothing.
The Languages
I'm not sure what's the best way to present my first impressions of these languages. I thought of doing tier lists based on different qualities like tooling or just how much I liked it. But I'm just going to summarize what I liked and didn't like about each language, and whether I would use it again in the future. I'll mostly make comparisons with JavaScript and Elixir, because those are the languages I have the most expertise in.
Clojure
I spent a good amount of time with Clojure. I installed Calva in VSCode, which provides paredit capabilities and inline code execution.
What I liked: I think that Lisps are conceptually really cool. They make lambda calculus a reality, and show that you can construct an entire program out of a single data structure. Clojure makes Lisps a little more approachable by providing more data structures out of the box.
What I didn't like: For as much time as I spent with Clojure, it never really clicked. I really wanted to like Clojure and Lisps in general, but the lack of syntax was often more of a hindrance to me. For example, I always had to think twice about the order of arguments after a comparison operator. In a language with infix operators, the symbols tell you the order. Macros are neat, but Elixir macros are nearly as powerful without being homoiconic.
Would I use again: I would love to give Clojure another shot in the future. Maybe I'll get to that "aha" moment someday.
OCaml
I have some previous experience with OCaml, and was familiar with many features from exposure to other ML languages like Haskell and Elm.
What I liked: Based on my previous exposure to Haskell, OCaml was refreshingly simple. There's really only a handful of ways of doing things. You've got your favorite pattern matching and algebraic data types, and life is good. OCaml is either gaining a lot of traction now, or I've been sinking into an OCaml echo-chamber.
What I didn't like: There was a period of time where everything stopped working for me in VSCode. I think something got messed up between Opam and the VSCode extension, or something. But the linter was telling me one thing, then I would try that out and it wouldn't compile. I came back to OCaml a few months later and everything was working fine again.
Would I use again: One of my goals for this year is to finally complete the excellent Crafting Interpreters book. I started implementing the interpreter in OCaml a while back instead of using Java as the book does. I definitely want to dive into OCaml more, and I suspect it might become one of my favorite languages in the near future.
Rust
I didn't get very far with Rust, so I can't really give it a fair assessment.
What I liked: I like the concept of Rust: a compiled language that can compete with the performance of programs written in C, but with a type system and compilation checks to avoid foot-guns. It is also kind of an ML in disguise, and has a great cross section of language features.
What I didn't like: I knew that Rust had a steep learning curve, and I certainly hit a wall pretty early on. I'm pretty sure I struggled for quite a while with the two different types of strings. I guess it's unfair to say that I didn't like the steep learning curve, because I really didn't put in enough time to get very far.
Would I use again: I was thinking of writing the second half of the Crafting Interpreters language in Rust. The book uses Java and then C, so I thought it would be cool to use OCaml and then Rust. Certain events in the Rust ecosystem and a plethora of alternatives (Nim, Zig, Odin, etc) might make me think twice, though.
Julia
Julia was on my original list, specifically because it was a neat cross section of compiled and data oriented languages, and boasted multiple dispatch. I didn't really spend enough time with it, though.
What I liked: As is expected with languages used for research or data processing (Python, R, Matlab, etc), the standard library is full of useful functions. The creator of Wolfram Alpha considers this a good thing, and his language is packed with features that come out of the box.
What I didn't like: Ironically, multiple dispatch ended up causing some issues for me at the beginning. I was trying to solve a problem using a function that returned a function, and the types for that ended up confusing the multiple dispatch mechanism. Higher order functions of that sort are kind of against the grain in Julia, so I just had to adapt my coding style. Also, the beloved pipe operator often gave me issues as well, and I often just gave up using it and nested my function calls.
Would I use again: I've been considering revisiting my simulation work for my master's thesis in mechanical engineering, which was written in Matlab. Julia would probably be a good alternative, so maybe I'll try it again.
Pharo (Smalltalk)
Pharo (Smalltalk) is one of those languages that had some seminal ideas that kind of got stuffed into other languages, but got left behind.
What I liked: Again, I think the concept of Smalltalk is intriguing. Beyond the pure message passing objects, there is also the idea that your 'code' is not just text, but the context and environment it is running in, including the editor itself. I do sometimes wonder if representing programs as text was a step backwards. So many early programing languages had graphical elements, and maybe that is a better representation for some problems.
What I didn't like: As much as I find the concept intriguing, it never really clicked with me. I think I'm on board with the message passing idea, but ironically, I think Erlang/Elixir is probably the best modern implementation of the concept. More on that in a future blog post.
Would I use again: Probably not. Maybe someone can convince me it's worth another look.
C++
I think I had some previous exposure to C++ at some point. As a self-taught software developer, I didn't have a traditional introduction, but I was familiar with the concepts of memory management and pointers and such.
What I liked: There are a ton of resources available for C++. Github Copilot also does a pretty good job at code completion (more on this later). C++ is probably not going away any time soon, and is the de-facto language for performant applications.
What I didn't like: I don't think I had the best tooling setup for C++ development. Each exercise required me to create a new build folder and generate makefiles. Maybe this is the standard way of doing things, but it was very tedious.
Would I use again: Sure, I guess so. If I ever had to.
Raku
While working on Perl version 6, too many new features were added to the language, and it was decided that they should just call it a different language. And thus Raku was born.
What I liked: Some things "just work", which aligns with the language goals. For example, sequences extrapolate really well automatically, and it's easy to map a function over a list using a neat operator. I've never tried APL or any other array programming language, but I feel like Raku could be a gateway drug into that world.
What I didn't like: I've heard good things about sigils, but I didn't really care for them. Defining variables with "my" was weird. There are many ways of doing the same thing, which was an intentional design choice, but I kind of prefer a smaller language with a limited way of doing things.
Would I use again: Probably not, but who knows?
Crystal
I ended up getting pretty far with Advent of Code in Crystal, so I think I got a fairly good exposure to the language. Crystal attempts to improve on Ruby by bringing the familiar syntax and expressiveness into a compiled and statically typed language.
What I liked: I was never indoctrinated in the OOP paradigm, and I've never really given it a fair shot, to be honest. Many of the exercises in Crystal required an OOP implementation, and for the first time, I kind of enjoyed how slick the solutions could look. I actually ended up using some OOP approaches while solving Advent of Code. Overall, Crystal was enjoyable to use. I've never used Ruby before, but maybe I was experiencing what Ruby programmers feel.
What I didn't like: Crystal doesn't have a tree sitter grammar yet, so the formatting wasn't great. Autocomplete was completely broken, to the point where it wouldn't even suggest variable names defined in the same file. I was playing with Lua around the same time as I started out with Crystal, and the compilation speed compared to the lightweight interpreter was quite a juxtaposition. Apparently, an interpreter for Crystal is in the works, but I never gave it a try. It's also strange to me that the constructor method is called initialize
, but you create objects by calling Object.new
.
Would I use again: Yeah, I actually enjoyed using Crystal. Both Crystal and Elixir are heavily inspired by Ruby, so although I've never actually used Ruby, I feel like maybe I have an idea of what it's like.
WebAssembly
WebAssembly had generated a lot of buzz, but it's kind of died down a little. It's definitely a big deal, but it's more of a foundational technology that will slowly change the web development landscape than a hot new framework that will last a few years.
What I liked: I really enjoyed solving problems with such a low level language. Unlike most assembly languages which are register based, WebAssembly is stack based, and it actually has loops and functions built in.
What I didn't like: It took me a while to get used to the S-expressions, and how to mix them with the standard stack based syntax. Conditional blocks were particularly confusing.
Would I use again: When I finally get around to building my own programming language, I definitely want to include WebAssembly as a target. I was thinking of creating a minimal Forth that "compiles" to WebAssembly.
Elm
I have observed Elm for a long time, but have been hesitant to give it a try due to questions about how it is managed and maintained. But the Exercism track for Elm is put together very well.
What I liked: I really like ML languages for some reason. Maybe it's because they popularized/introduced pattern matching and algebraic data types, particularly options/maybes. Elm was easy to pick up and use.
What I didn't like: I am glad Elm has an opinionated formatter, but it is a separate thing to be installed along with the test runner. Also, I am not a fan of the standard format, but that's just my opinion. I'm still skeptical about the future of Elm, but I'm glad that it exists and for the influence it has had on the web development arena.
Would I use again: I probably wouldn't try to introduce Elm into my workplace. But I would consider using it for a side project or work for a company that is already using it.
Lua
Conceptually, Lua is very similar to some ideas I've had for a toy language. Particularly the table data structure, where objects and arrays are stuffed into a single data structure. I've since figured out why you might not want that (it's not a very efficient implementation of either data structure), but it's still a neat idea to entertain.
What I liked: The Lua interpreter I used was super fast. Running the tests always happened in milliseconds. I'm sure there is only so much Lua can handle, but for small programs (embedded logic), it does really well.
What I didn't like: The docs were absolutely horrendous. Maybe I was looking in the wrong places, but it was so hard to find what I was looking for. Also, the standard library is tiny. This is probably by design, but I felt like I was reinventing the wheel in many cases.
Would I use again: Lua gets used in various places (Neovim configuration, some game engines, etc.), so I'll probably run into it in the future.
Gleam
Gleam is a new language that compiles to the BEAM virtual machine, which is what runs Erlang/Elixir. It's great to have another contender for the BEAM, especially one with a more ML feel.
What I liked: On paper, Gleam has the potential of becoming my favorite language. It is an ML with a C-style syntax (curly braces), static types, and great concurrency because of the BEAM.
What I didn't like: It's too early to get a good idea of the success of this language. Gleam doesn't have if statements, and the only method of conditional logic is pattern matching with case
. I had thought of doing the same thing in a toy language, but sometimes I just wanted an inline ternary expression.
Would I use again: I'll definitely be keeping an eye on Gleam, and eagerly see where it goes from here.
Follow the grain
Solving exercises on Exercism won't necessarily teach you how to build real world applications, but it's a good way to learn the syntax and semantics of the language. I often approached an exercise with my existing strategies, and realized that I needed to adjust my approach to follow the grain of the language. For example, after solving a fizz buzz style question in OCaml using partial application, I tried that in Rust. But Rust does not make it easy to return functions from functions, so I realized in needed a different approach. Just because you can do something a certain way in a language, doesn't mean you should. This is what it means to write idiomatic code: code that follows the grain of the language.
Github Copilot
There were periods throughout the year where I had Copilot on in VSCode. It did an amazing job with generating C++ code, but for most of the other languages, it was spotty. Arguably, using Copilot defeats the purpose of using exercises to learn a new language, but I think it aided the learning process to some extent.
Conclusion
Throughout the year, I was exposed to many new ideas in programming. I dove into OOP, systems programming, low level assembly, statically and dynamically typed languages, and a whole mixed bag of syntax and semantics.
In 2024, there is a new challenge to solve a specific exercise every week. Instead of joining that, I hope to apply what I've learned in designing my own toy programming languages, or maybe even getting involved in the development of existing languages such as Roc or Gleam.
Top comments (0)