Learning a new language. Again.
Ever since I read The Pragmatic Programmer around ten years ago I’ve continually tried to challenge myself into learning a new programming language regularly. And every time I take on this challenge my progress seems to follow the same pattern.
My first reaction is always one of excitement. The more different the new language is from previous languages I have explored, the higher the level of excitement. Smalltalk still ranks as number one in this regard: the purity, simplicity and expressiveness still blows me away. The same goes for LISP; the way you can express anything in LISP, even invent a completely new language for yourself probably explains why LISP has survived all its successors.
The next reaction which fuels my learning process is one of changing how I approach programming itself. Purely functional or object-oriented languages appeal to the perfectionist within me, and changes the way I use my current languages and tools. This phase, where the excitement fades, is one of reflection. There is always an underlying wish of throwing everything I use from day to day away, replacing it with the new and shiny. But since this is seldom a realistic option, applying new tricks from a different language into my current one feels like a good way of applying knowledge in a way that benefits me in my day-to-day work. Just like working on a project in one domain will change the way I approach problems in other domains.
At this point my exploration of a new language usually ends, and the language joins my continually growing list of things to explore further when time allows. Throughout my career this backlog of half-explored languages has caused me quite a lot of stress. I never really learned Erlang, Elixir, Clojure, Swift or Scala well enough. There’s still so much to learn from these languages, and here I am working in other languages.
This is OK.
I didn’t waste my time half-learning these languages, they just haven’t fully stuck with me yet. When I need to throw up a quick API proxy for one of my projects, I still turn to Ruby. I even use Rails, even though I know the pain it’s going to cause me down the line. But for every new language that I half-learn, the way I write Ruby changes. And the fact that Ruby lets me do things like write in a more functional manner than I first started out is the main reason I stick with Ruby. The paradox is that Ruby’s hybrid-ness, supporting Perl’s magic variables alongside OOP and functional programming, was my main objection to the language when I started using it. I still cringe from warts like $_, $0 and BEGIN/END in Ruby. I felt, and still feel from time to time, that these features are a betrayal to Ruby’s elegant OOP model. And still they let me experiment with new concepts in a language I know, and on projects I work on every day.
Over the last few weeks I’ve played around with Crystal, which shares most of its syntax with Ruby. This introduction, by the author of a web framework for Crystal, caught my attention. While Crystal hasn’t fuelled the level of excitement that I got from Elixir or Clojure, it has allowed me to explore some other concepts from modern languages that I’ve been wanting to learn more about and which feel highly relevant today:
- Static typing and type inference. The war between duck typing and static typing seems to divide the programming community. While duck typing enthusiasts will tell you that static typing doesn’t remove the need for unit tests, anyone who tries a static typed language will see that the compiler does help you eliminate bugs in your code at compile time. Crystal is statically typed, but will infer types for you at compile time, saving you from the baroque enchantments of writing Java.
- A channel based concurrency model. Concurrency is a big topic these days, as Moore’s law relies on increasing the number of processors rather than the processors themselves becoming faster these days. Crystal offers channels for concurrency, which is a much easier way of writing concurrent code than using threads.
- Integration with native code is really easy with Crystal. While FFI makes it a lot easier than writing native extensions the old way, Crystal’s approach requires virtually no C code at all.
- Crystal complies to native code. The way Go compiles to native code fascinated me. It’s probably because I never really wrote much C, but I always envied C/C++ programmers their ability to build a proper native application. Crystal uses LLVM as part of its toolchain, and compiles to highly performant, native code.
- Crystal is self-hosted. Crystal itself, along with its compiler, is written in Crystal. Apart from appealing to my sense of elegance gives the language authors a big incentive to make Crystal fast.
- Macros. While there are various meta programming facilities in Ruby, allowing me to generate code on the fly, macros are evaluated at compile time and let me generate code in a controlled manner.
- Tons of nice touches, like Flags Enums. Although things like enums can easily be replaced with other constructs, I kept thinking “what a nice touch” while reading the Crystal documentation.
Learning Crystal literally took me a couple of hours. At this point I was able to start writing code. I’ve found Crystal’s standard library to include all the batteries I needed. The second evening of writing Crystal I deployed my first app to Heroku.
At this point I’m still at the “when all you have is a hammer, the whole world looks like a nail” phase in my process of getting to know Crystal. But I can’t recall ever having learned a language which allowed me to start writing apps this quickly before. And this feels significant.
I have no idea where this will take me. For all I know, my exploration of Crystal might end right here, with this blog post.
If it does, I still learned something significant.