Day 1: Why Raku is the ideal language for Advent of Code

Now that it’s December, it’s time for two of my favorite traditions from the tech world: the Raku Advent Calendar and Advent of Code. These two holiday traditions have a fair amount in common – they both run from December 1 through Christmas, and both involve releasing something new every day during the event. Specifically, the Raku Advent Calendar releases a new blog post about the Raku programming language, while Advent of Code releases a new programming challenge – which can be solved in any language.

(In this post, I’ll be referring to Advent of Code as “AoC” – not to be confused with the American politician AOC who, to the best of my knowledge, does not program in Raku.)

For me, Raku and AoC are the chocolate and peanut butter of tech Advent season: each is great on its own, but they’re even better in combination. If your only goal is to solve AoC challenges, Raku is a great language to use; on the other hand, if your only goal is to learn Raku, then solving AoC challenges is a great way to do so. This post will explain how Raku and AoC are such a good fit and then provide some resources to help us all get started solving AoC challenges.

What is Raku? (And why should you care?)

Since Raku is a relatively new programming language, at least some of you may not be familiar with it or why it’s worth learning. Raku is notoriously hard to pin down in a single sentence, but here’s my attempt:

Raku A concise, expressive, aggressively multiparadigm language with strongly inferred types, built-in concurrency, rich metaprogramming, and best-in-class string processing and pattern matching.

What this means in practice is that I find myself reaching for Raku increasingly often. When faced with pretty much any problem, I keep concluding that Raku is the language that will let me solve it in the clearest, fastest, and most elegant way possible. (The one exception is if solving my problem demands the raw speed or low resource use of a compiled language. But even in that relatively rare case, I’d probably write the performance-critical sections of my code in Rust and the rest in Raku, taking advantage of how well the two languages play together.)

That’s not to say that Raku is a language that tries to be all things to all people. In fact, Raku has a rare, laser-like focus on individual productivity and is willing to trade off some standard enterprise/large group features to achieve that goal, as I’ve previously discussed at length (part 1, part 2, part 3). But, even setting those big-picture ideas aside, Raku is a language that’s full of interesting ideas. If those ideas are even half as good as I believe them to be, then Raku is a language you’ll want in your toolbelt.

Why is Raku a great fit for solving AoC challenges?

I believe that Raku is a good fit for solving many different problems, but it’s definitely an excellent fit for Advent of Code challenges. To explain why, I’ll walk through what we’d want out of an ideal AoC language. Then I’ll present a Raku solution to last year’s first AoC challenge and compare it to our ideal. (Spoiler: they’re really similar!)

When thinking about the ideal AoC language, the first feature of AoC that comes to mind is that it’s a series of small, largely self-contained puzzles rather than one large project. This suggests that the ideal language would be concise and low-boilerplate. It’s annoying but bearable to deal with many lines of boilerplate when setting up a large project, but it’d be far worse to do so repeatedly on each day of AoC. And, since we’ll be sharing our code, keeping it concise will help others see our logic without being distracted by housekeeping details.

We can be a bit more specific: the AoC challenges typically provide textual input and look for textual output; they also usually provide several test cases that can help to craft a working solution. Thus, in addition to being concise in general, our ideal language should offer low-boilerplate solutions for scripting and testing.

Perhaps the most notable (and certainly most fun!) feature of AoC is that it’s community driven and educational: thousands of programmers are all solving the same puzzles on more or less the same schedule, and are then posting their solutions to the Advent of Code subreddit. It’s very common to learn as much or more from reading other solutions – including ones in different languages – as you do from solving the challenge yourself. This means that our ideal language should be readable and elegant, even for people without much experience in the language.

Of course, while it’s great to be able to write code that people unfamiliar with the language can appreciate, much of our teaching and learning will come from comparing our solutions to other solutions in the same language. After all, different solutions in the same language will often show approaches that make different tradeoffs and can help expand our programming toolset. Or at least that happens if different solutions in our language are, well, different: if our language pushes everyone towards a single, obvious solution, then there will be much less room for that sort of learning. So our ideal language should offer more than one way to solve any particular challenge.

I’m sure I could go on, but it seems like we have a pretty good list. To sum up, we’re looking for a language that’s concise and low-boilerplate, especially for scripting and testing, and that allows for multiple different readable and elegant solutions to each challenge. (Note that this list is about finding the best language for learning from and enjoying AoC. If your goal is to place highly on the AoC leaderboard, then Raku’s excellent string processing features would still make it a good fit. But, realistically, if that’s your goal, you should pick whatever language you know best.)

Now that we know what we’d want in an ideal language, let’s take a look at a Raku solution to last year’s first challenge.

AoC 2019 day 1 in Raku

You can read the full problem description for all the details, but the short version is that this challenge asks us to make a few different calculations about the fuel required to launch a spaceship based on its mass. Specifically, in Part 1 we are told:

to find the fuel required for a module, take its mass, divide by three, round down, and subtract 2.

Because Raku has an integer division operator, this is almost trivially easy:

sub fuel($mass) { +$mass div 3 - 2 }

Part 2 asks us to perform a similar calculation, but this time to take into account the extra mass added by the fuel we’re adding:

Fuel itself requires fuel just like a module – take its mass, divide by three, round down, and subtract 2. However, that fuel also requires fuel, and that fuel requires fuel, and so on.

To solve this part, we can use our fuel function from Part 1 to calculate how much fuel we need, and then add our initial result to the amount of fuel we need for the new mass.

multi total-fuel($mass) { fuel($mass).&{$_ + .&total-fuel} }

Part 2 also tells us:

Any mass that would require negative fuel should instead be treated as if it requires zero fuel.

Again, Raku offers a powerful feature that makes this simple: in this case, the powerful feature is Raku’s ability to pattern match against run-time values in function signatures.

multi total-fuel($mass where fuel($mass) ≤ 0) { 0 }

With those three lines, we’ve essentially solved the challenge. Of course, we want to be able to perform this calculation not just on single numbers but on our entire input (which, for this challenge, takes the form of a text file with a different number on each line). We also want our script to be executable and to expose a CLI with a user-friendly description and --help text. This CLI should allow the user to select whether our script solves Part 1 or Part 2 of the challenge.

Fortunately, Raku lets us add all of these niceties with a grand total of three additional lines of code and a shebang comment. This gets our full solution so far to the following:

#!/usr/bin/env raku
unit sub MAIN( #= Solve the 2019 AoC day 01 puzzle
Bool :$p2 #={ Solve p2 instead of p1 (the default)} );
sub fuel($mass) { +$mass div 3 - 2 }
multi total-fuel($mass) { fuel($mass).&{$_ + .&total-fuel} }
multi total-fuel($mass where fuel($mass) ≤ 0) { 0 }
say lines.map($p2 ?? &total-fuel !! &fuel).sum;

(The comments embedded in lines 2 and 3 produce the --help documentation.)

The challenge also provides 7 test cases that we should probably include. When working on a larger Raku project, the standard approach would be to split our tests out into a separate file. But we’re going scripting and it would be a shame to give up our single-file simplicity. So, instead of using a separate file, we’ll use a technique that I previously blogged about to use Raku’s conditional compilation to include our tests in a single file without executing them every time we run our script.

Using that technique, here’s what we get for our final code, including tests:

#!/usr/bin/env raku
unit sub MAIN( #= Solve the 2019 AoC day 01 puzzle
Bool :$p2 #={ Solve p2 instead of p1 (the default)} );
sub fuel($mass) { +$mass div 3 - 2 }
multi total-fuel($mass) { fuel($mass).&{$_ + .&total-fuel} }
multi total-fuel($mass where fuel($mass) ≤ 0) { 0 }
say lines.map($p2 ?? &total-fuel !! &fuel).sum;
# Tests (run with `raku --doc -c $FILE`)
DOC CHECK { use Test;
subtest 'Part 1', { fuel(12).&is: 2;
fuel(14).&is: 2;
fuel(1_969).&is: 654;
fuel(100_756).&is: 33_583; }
subtest 'Part 2', { total-fuel(14).&is: 2;
total-fuel(1_969).&is: 966;
total-fuel(100_756).&is: 50_346; }
}

Comparing Raku to the ideal AoC language

So, how does Raku do on the metrics we came up with earlier? Well, in terms of being concise and low-boilerplate, this code seems to do reasonably well. True, it’s far from maximally concise; it’s about 7 times longer than the solution I came up with when I worked through this challenge last year in Dyalog APL. However, at 6 lines of code for the program and 9 for the 7 test cases, I’d still score it as highly concise. As a point of comparison, a strong Rust solution used 27 lines of code on the solution and 17 on the tests.

And when it comes to supporting scripting and testing while eliminating boilerplate, the Raku code is just about ideal. The shebang line at the beginning is boilerplate, but is essential to creating a standalone script in any language. Other than that line, the only bits that are even arguably boilerplate are the use Test line and the unit sub MAIN line. These lines give us a tested script with a fully documented CLI – something that neither the Rust nor the APL examples linked above provide. Considering what we get in return, I’m prepared to give this solution full marks for supporting scripting and testing without relying on boilerplate.

Judging how readable and elegant the Raku solution is certainly involves more subjectivity. And, arguably, someone who knows Raku is least qualified to judge how readable the code would be to a programmer who doesn’t know the language. That said, in my view this solution is readable enough that a non-Raku programmer could follow it – while still making use of enough of Raku’s clever features to be elegant. A non-Raku programmer probably wouldn’t know the div operator, but could likely figure it out from the context and the reasonable assumption that it must do something different from the / operator. The fuel($mass).&{ $_ + total-fuel($_) } line might also slow a non-Raku programmer down – especially if they hadn’t encountered the $_ topic variable in Perl. But, again, I believe they could work it out quickly enough from the context. And, once they do, I bet they’d appreciate the elegance of reusing (rather than recalculating) the fuel($mass) value without needing to create and name a temporary value.

The last snippet that would likely be unfamiliar to non-Raku programmers is the $mass where fuel($mass) ≤ 0 bit. But, in my (admittedly biased!) opinion, this snippet is both immediately clear and immediately elegant – it so perfectly captures the intent of “call this function only when $mass is less than or equal to 0” in a way that most other programming languages cannot express in a function signature. I know that elegance is in the eye of the beholder, but this snippet and this script as a whole fit my definition pretty much perfectly.

Our final criterion – whether Raku offers more than one way to solve this challenge – is inherently impossible to judge from a single solution. But there’s always more than one way to do it in Raku, regardless of what it refers to. One relatively minor change would be to lean into Raku’s type system. Because Raku has such capable type inference, it is possible to write it just as you’d write a dynamically typed language. But Raku has a powerful type system and allows you to constrain the types your functions accept.

For example, in the current code, we have the function sub fuel($mass) { +$mass div 3 - 2 }. This accepts an arugment of any type (which it later casts to a numeric type with the + operator) and makes no guarantee of its return type. This flexibility is nice – it lets us call fuel with a Str when we pass it input from stdin but with an Int when we call it recursivly. If we wanted more type safety, though (and we very well might in a longer program), we could pin down both the parameter and return types like this:

sub fuel(Int $mass --> Int) { $mass div 3 - 2 }

Since this only accepts Ints, we wouldn’t need the + inside the function body, but we would need to parse the input before calling fuel. We could do so in many ways; I’d probably add a .map(+*) method call to our lines pipeline.

Another option would strike something of a middle ground between safety and flexibility using coercion type constraints. This would result in a signature of sub fuel(Int() $mass --> Int). The Int() bit constrains the function to accept a type that can be cast to an Int and automatically performs the cast. Using this signature, we’d avoid the need for either +$mass or .map(+*).

Adding type safety is just one way we could approach this problem differently. We could also use a sequence with a computed endpoint (e.g., $_, total-fuel($_) …^ * ≤ 0) as an alternative to the where block we currently use. Or we could use reduce (either as a method call or with the ]) instead of separate map and sum steps. Or we could step even further away from map by using gather and take. In short, even with a problem as simple as this, there are a vast number of ways we could use Raku – and that’s without mentioning any of the ways we could use Raku’s strong, built-in support for concurrency to parallelize our solution if, for some reason, we wanted to push performance. Regardless of how you personally solve an AoC challenge, I’m willing to bet that you’ll be able to learn something by taking a look at the variety of other solutions people come up with in Raku.

I know that there’s a limit to how many conclusions we can draw from looking at a single AoC challenge – especially since the challenges generally increase in difficulty through December, which means Day 1 is not all that representative. And I know that not everyone will share my exact definition of what makes a language a good fit for AoC. Nevertheless, I hope that our fairly detailed examination of an AoC challenge has been enough to persuade you that Raku is a promising language to use for AoC. If you already know at least some Raku, I hope you’ll agree that working through the AoC challenges could be a good use of your time (both to get better at Raku and to share your knowledge). And if you don’t yet know Raku, I hope you’re at least tempted to use AoC as an opportunity to learn.

If so, I have some good news: AoC is an excellent way to learn Raku.

Why is AoC a great way to learn Raku?

To see why AoC is such a good way to learn Raku, I’d like to start with a different question: how hard is it to learn Raku?

There are two different perspectives on this question. From one perspective, Raku is extremely hard to learn – in pretty much exactly the way that Scheme is easy to learn. Scheme famously has almost no syntax; you can easily sit down and learn the entirety of Scheme’s syntax in a single sitting. (Of course mastering the language would take far longer.)

Raku occupies nearly the opposite extreme: it makes extensive use of syntax. I don’t think there’s any particularly principled way to measure the “size” of different programming languages, but the Learn X in Y minutes series of guides might provide a rough approximation. Comparing the Learn X in Y minutes, where X=CHICKEN Scheme guide to the Learn X in Y minutes, where X=Raku, I see that the Raku one has about 7× as many lines; I can understand why someone might conclude that the value of Y is very different in those two equations.

And syntax isn’t the only way that Raku is a large language. I previously described Raku as “aggressively multiparadigm”; an influential review called Raku “multi-paradigm, maybe omni-paradigm”. However you put it, it’s clear that you can write Raku in many different ways. You can write it procedurally, complete with explicit for loops if you’d like. It also has first-class support for functional programming (including support for function types, abstract data types, and other advanced functional features that multiparadigm languages often omit from their functional toolbelt). You can also write purely object-oriented Raku and indeed the language itself is built from an OO perspective. Raku also borrows many ideas from array programming, from constraint programming, and from dataflow programming; it supports concurrent programming, generic programming, and extremely strong introspection and metaprogramming.

Truly mastering Raku doesn’t just mean knowing all of its extensive syntax; it doesn’t even mean becoming familiar with the many different paradigms that Raku supports. To be fully proficient with Raku requires the judgment to decide which paradigm best fits today’s problem and the comfort to correctly apply that paradigm. From this perspective, Raku is an extremely hard language to learn – though the rewards of doing so make it worthwhile.

But there’s another perspective that says that Raku is actually easy to learn.

True, it’s hard to learn all of Raku’s syntax if you try to learn it all at once, without context. But you shouldn’t do that, any more than an English speaker would try to learn German by sitting down with a German dictionary. The only reason that learning all of a language’s syntax seems like a reasonable thing to do is because some programming languages are so minimal that an all-at-once approach is not immediately disastrous. But that doesn’t make that approach a good idea.

Instead, the way to learn Raku it to learn just enough to get by, and then to start using it and picking up more as you go. Viewed from this perspective, Raku’s large amount of syntax is irrelevant to how hard the language is to learn – you’re not trying to learn it all at once, so it doesn’t matter if the bit you start with is half the syntax or just 20%. And Raku’s multiparadigm nature actually makes Raku easier to lean rather than harder: because Raku supports so many different paradigms, you can start with whatever subset of Raku you’re initially most comfortable with and expand out from there.

By now, you’ve probably figured out why we’ve spent so long discussing whether learning Raku is easy or hard: If you try to learn Raku the way you might learn Scheme, you’ll be in for a hard slog. But if you take a more piecemeal approach, learning Raku is much easier. And that piecemeal approach maps perfectly onto learning Raku through Advent of Code.

Specifically, to succeed with the piecemeal approach, you need to work on a series of small projects, each of which is manageable even if you know only a corner of the language. They need to be projects with relatively low stakes – since you won’t yet know all the different ways to solve a problem, odds are good that you’ll sometimes implement a suboptimal solution. And, most importantly of all, you need to work through them in a context that lets you see alternate approaches. Starting with the object-oriented subset of Raku and gradually adding techniques from outside your comfort zone is a great way to learn Raku, but it only works if you actually do the “gradually add techniques” part. If you get stuck in a rut, you won’t learn nearly as much, and seeing other people use different techniques to solve the same problem more elegantly is the best way to avoid that sort of rut.

In short, if you’d like to get better at Raku, there’s really no better way than by working through as many of the Advent of Code challenges as you can, comparing and contrasting your solutions with other AoC solutions (in Raku and in other languages), and thinking critically about the advantages and disadvantages of various approaches.

Let’s do this together

To make it easier for us to all find each other’s Raku Advent of Code solutions, I’ve created a GitHub repo that can hold them all: codesections/advent-of-raku-2020. I’ll be keeping the repo intentionally minimalist – if Raku weren’t so good at avoiding boilerplate, I’d add a template for setting up an AoC solution. But, given how little boilerplate Raku requires, I plan to keep the repo as simply a host for our solutions and links to related resources.

If you would like your solutions to be included, please submit a PR that adds a $your-name folder that includes your solutions (details are in the README). Of course, posting your solutions to the Advent of Raku repo shouldn’t stop you from posting them anywhere else you might want to, whether that’s your own site, the Raku subreddit or the main AoC subreddit.

And please feel free to add your solutions to the repo even – especially! – if you aren’t sure that you’ll have time to complete all the AoC challenges or if you’re brand new to Raku and aren’t positive that you’ll stick with the language past day 1. The point of all of this is to learn together, after all, and we can do that best by bringing together people with as wide a set of perspectives and backgrounds as possible.

I’ve also registered a private leaderboard for the Raku community, which you can access by logging in, following that link and then entering the code 407451-52d64a27 (if necessary, I can change that code and distribute it more securely, but I don’t anticipate any issues). Despite the “leaderboard” name, I view this as less a competitive ranking and more a way to keep track of who is participating – speaking personally, I have no intention of rushing to finish the challenges or trying to increase my score on any of the time-based metrics (indeed, given my timezone and the schedule I typically keep, I doubt I’ll start most puzzles until hours after they’re released).

I look forward to seeing your solutions; I’m sure we can learn a lot from one another. I also look forward to discussing the different approaches we take, whether that conversation takes place in the GitHub issues, on the Raku or AoC subreddits, or on the #raku IRC channel (I’ll try to keep an eye on all of those). Good luck to everyone, and may we all have an -Ofun Advent.

Published by codesections

Free software developer working primarily in Raku and Rust. Former attorney at Davis Polk & Wardwell LLP; current member of the Raku Steering Council and The Perl & Raku Foundation board. My professional interests include web development, open source, and making things as simple as possible. website: www.codesections.com

3 thoughts on “Day 1: Why Raku is the ideal language for Advent of Code

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: