Blogi

Advent of Code 2020 and Ballerina

Kirjoittanut Jani Hurskainen | Senior Integration Developer | 5.5.2021 21:00

 

Have you heard about Advent of Code or tried the cloud-native programming language Ballerina? In his blog, Digia's Senior Integration Developer Jani Hurskainen looks at programming puzzles, and yet another emerging programming language and ponders how they fit together. 

About Advent of code

Advent of Code is an Advent calendar of small Christmas-themed puzzles intended to be solved by programming. Personally, I have never been into programming puzzles but when I checked Advent of Code the first time in early December 2018 I was hooked immediately. Since then I have run a small competition here at Digia for my colleagues.

Why do I like Advent of Code? The background story is hilarious, the aesthetics of the web site hits my nostalgia sweet spot and the site runs smoothly (except when you underestimate your popularity). And of course, the puzzles are just brilliant in their simplicity! It’s amazing how the puzzle input (pre-generated and pooled, so not everybody has unique input) has carefully selected edge cases and only one solution. Another fine aspect of the puzzles is that the entry level is quite low and advanced computer science is not necessary. It’s also possible to solve puzzles in very different ways. It is amazing that everything is created by just one guy, Eric Wastl

As far as I can say knowing the basics of one programming language, a little bit of logical thinking and  basic mathematicsrecursiongraph traversal and associative arrays takes you far. Or at least somewhere around day 10 is as far as I usually get. It just happens that at that point I have used my time quota and unfortunately must give up 🙁.

The main reasons I have not gotten further yet is that there has always been one tricky puzzle that takes a lot of time to because of the math behind the solution. In my daily work I don’t use a general purpose programming language so I have always used programming languages (D, Ballerina and V) which are new to me. The new languages are fun to learn but the unfortunate drawbacks to finish the puzzle are the learning curve, every language has its own set of pitfalls and even compiler bugs, as two of the languages used so far are not yet mature. I also like to solve the puzzles in order so when I hit the roadblock I put all my focus to work my way out, even if from the competitive point of view it would be more rational to skip the puzzle and try to score another one instead.

My Advent of Code workflow 

Surprisingly, I start by reading the puzzle and trying to understand all the details in it. I have learned to be careful here as I have lost several hours of effort by accidentally skipping minor details in the problem description.

Every puzzle has an example input that is used to explain the puzzle resolution logic. I use example data to build a unit test case. Sometimes I even add new test cases when I see that the example is missing some obvious edge cases that I’m likely to get wrong. E.g., off-by-one errors are not so rare… But more often than not if the one and only unit test passes then also the actual input is calculated correctly!

The last step before the actual coding is to figure out how to solve the puzzle with pen and paper. This step varies a lot depending on my skillset. Sometimes the solution is obvious and sometimes it’s not. But it is fun to notice that one becomes better at solving puzzles by solving puzzles!

I must confess that in my Advent of Code career since 2018 I have “cheated” three times, either by reading Reddit or other people’s solutions before I had finished my own version. Two times it has been about mathematics and one time I didn’t even understand the problem description until I read the other people’s implementations. It is also stunning to learn that usually there are far more elegant solutions to the problem than what I have figured out. But every time this has been a great learning opportunity!

About Ballerina 

Ballerina is a cloud-native programming language that is designed for applications that run in the cloud and integrate with data sources, services and network-connected APIs. Ballerina is an open-source project (Apache License) started and developed by WSO2 that is a globally established provider of API management and integration solutions. The first major version (1.0.0) was released on 9th September 2019 and at the time of the writing the second major version (Swan Lake, so no more numbers but names) is scheduled to be released in 2021.

The fundamental feature that makes Ballerina unique is the abstractions of networking concepts such as client objects, services, resource functions, and listeners that are part of the language syntax. Under the hood Ballerina uses non-blocking I/O for remote calls but the developer doesn’t have to worry about all the complications and can use language-provided constructs instead. For example, the constructs include servicecircuit breaker and retry. If you want to go into the details, please check Reactive Microservices Done Right! by Anjana Fernando. 

On top of network abstractions, Ballerina has plenty of other seductive features too, such as:  

  • autogenerated sequence diagrams from the code 
  • semantically-rich static program model with concurrent environment and native support for JSON, XML, objects and procedural and functional programming styles 
  • a comprehensive tool chain for software builds, document generation, unit testing and deploying Ballerina code to different cloud native run-times 
  • seamless connectivity with any modern cloud end-point such as HTTP/HTTPS/HTTP2, WebSocket, WebSub, database, gRPC, NATS, RabbitMQ, Kafka, etc … 
  • a built-in observability 
  • a large and comprehensive standard library 

The main learning resources can be found in the Let’s learn Ballerina! section at the Ballerina website.

Advent of Code and Ballerina 

Solving Advent of Code puzzles requires only a subset of Ballerina language features. But even if we ignore all that fancy cloud native stuff, Ballerina is still a decent general-purpose programming language and very suitable for Advent of Code.

To give you a taste of Ballerina, I will go below through a few Ballerina language features that you will most likely run into in the very first puzzles. The full code can be found at https://bitbucket.org/janihur/advent-of-code/ where all my code for Advent of Code exists.

File IO and Functional Iteration 

On day 1 I noticed I do not know how to read data from a file so in the beginning I just hardcoded the input and figured how to read files later. I also failed to see the difference between “.” (period) and “:” (semicolon) in 'int:fromString and lost again several minutes. Oh, and the algorithm is simply trying all combinations in a loop until a (guaranteed) match is found.

Below is a code snippet on how to read a text record data from a file. This is very much the boilerplate I use to read the puzzle input:

// read puzzle data ------------------------------------------------------- 

io:ReadableByteChannel byteChannel =  

  checkpanic io:openReadableFile("../../inputs/2020-01"); 

io:ReadableCharacterChannel characterChannel = new (byteChannel"UTF-8"); 

io:ReadableTextRecordChannel delimitedRecordChannel =  

  new (characterChannelrs = "\\n", fs = "\\n"); 

 
string[][] puzzleDataStr = []; 

while (delimitedRecordChannel.hasNext()) { 

  string[] records = checkpanic delimitedRecordChannel.getNext(); 

  puzzleDataStr.push(records); 

} 

 

int[puzzleData = puzzleDataStr.map(function (string[recordsreturns int { 

  return checkpanic 'int:fromString(records[0]); 

}); 

Ballerina provides plenty of different abstractions in terms of IO channels to read different kind of data such as records, CSV, JSON and XML. In the above, you see how I used byte, character and text record channels to read the contents of a file into a two-dimensional array of strings. In this case, the input contains only one number per line so the data structure string[][] puzzleDataStr looks like:

[ 

  ["1765"], 

  ["1742"], 

  ["1756"], 

  ["1688"] 

] 

Then I simply convert the strings to an array of integers (int[] puzzleData). Array is an iterable type (and you can make your own types iterable too) and thus it supports functional iteration operations such as map, filter and reduce in the similar fashion to Python or in JavaScript.

Ballerina functions are first-class citizens so they can be passed as a parameter to other functions and functions can also return functions. In puzzleDataStr.map() I use an anonymous function to convert the string into an integer:

function (string[] records) returns int { 

  return checkpanic 'int:fromString(records[0]); 

} 

This is also the most verbose (annoying) part of the otherwise concise Ballerina syntax (concise when compared to Java not when compared to functional languages) - the function types must always be written explicitly.

You might wonder about the checkpanic. No worries - that will be covered in the next chapter!

Record Type 

Here we have a little bit more complex input data, so I decided to convert that into a record type:

// record type descriptor 

type PasswordRequirement record {| 

    string password; 

    int minOccurences; 

    int maxOccurences; 

    string requiredLetter; 

|};

Ballerina records have a plethora of features such as: 

  • A record can be either open or closed. Open records don’t have a fixed set of fields, but new fields can be added later. Closed records can’t be expanded later. 
  • Anonymous records. 
  • Variable references. 
  • Optional and immutable fields. 
  • Default field values. 
  • Record type inclusionA convenient syntax for record type descriptors to include all field definitions from another record type descriptor. 

PasswordRequirement is a closed record (record {||}and it doesn’t use any other features listed above. 

The feature set of a record also illustrates the point that Ballerina is a large and complex language. But thankfully it has been designed to have easy-to-learn subsets, so you don’t have to master everything from day one. Instead, you can incrementally expand your horizons as needs arise.

The input data is converted to an array of PasswordRequirement records with the map function we are already familiar with:

PasswordRequirement[] puzzleData = puzzleDataStr.map(function (string[] records) returns PasswordRequirement { 

    int splitPoint = <int>records[0].indexOf("-", 0); 

    return { 

        password: records[2], 

        minOccurencescheckpanic 'int:fromString(records[0].substring(0, splitPoint)), 

        maxOccurencescheckpanic 'int:fromString(records[0].substring(splitPoint + 1)), 

        requiredLetter: records[1].substring(0,1) 

    }; 

}); 

Error Model 

And now back to checkpanic! In Advent of Code we don’t need to care much about different error scenarios but I give you an idea about how Ballerina treats errors as an error handling strategy is one of the most important things of a programming language. Ballerina makes the difference between two different kinds of error categories as described in The Error Model by Joe Duffy:

  1. recoverable errors (in Ballerina these are called ordinary errors) 
  2. bugs (in Ballerina these are called exceptional conditions) 

Recoverable (or ordinary) errors are expected to be taken care in the Ballerina code by the programmer. These errors can’t be ignored (the compiler will raise an error if you try), but they can be suppressed by writing code that does essentially nothing. Ballerina provides language constructs to propagate the error value so the explicit error value handling doesn’t clutter the actual program control flow. The language constructs that support recoverable error processing are:

Below you’ll find a simple commented example that illustrates how the above listed constructs can be used.  

// this program prints: 

// business_process(10, 2) = 6 

// business_process(10, 0) = error("DIVISION_BY_ZERO") 

 

import ballerina/io; 

 

// returns a union type of 

// int - in case of success 

// error - in case of failure 

function div(int a, int b) returns int|error { 

    if b == 0 { 

        return error("DIVISION_BY_ZERO"); // error value 

    } 

    return a / b; 

} 

 

function business_process(int a, int b) returns int|error { 

    // check expression checks the value of the subexpression and 

    // returns immediately if the result is a type of error 

    // so check is a short-hand for the following pseudocode 

    // int c = if subexpression value is error { 

    //     return subexpression; // an error 

    // } else { 

    //     subexpression value; // an int 

    // } 

    int c = check div(a, b); // returns error immediately if div() returns error 

    c = c + 1; // this line is executed only if div() returns int 

    return c; 

} 

 

public function main() { 

    int|error c1 = business_process(10, 2); 

    // ballerina enforces programmer to explicitly process all possible return types 

    // error conditions can't be ignored but are compilation failures 

    if c1 is error { // type guard 

        io:println(string`business_process(10, 2) = ${c1.toString()}`); 

    } else { 

        io:println(string`business_process(10, 2) = ${c1}`); 

    } 

     

    int|error c2 = business_process(10, 0); 

    if c2 is error { 

        io:println(string`business_process(10, 0) = ${c2.toString()}`); 

    } else { 

        io:println(string`business_process(10, 0) = ${c2}`); 

    } 

} 

For exceptional conditions the recommended Ballerina practice is to panic. A Ballerina panic is nothing more but the good old exception. When a Ballerina program panics (i.e. throws or raises), a stack unwinding is started and the program is terminated if the panic (and the underlying error object) is not trapped (caught) by the program. Panics (exceptions) should only be used in cases where the program can’t handle the error.

Below you will find a variation of the previous example to illustrate the Ballerina exception mechanics.

// this program prints:

// business_process(10, 2) = 6

// business_process(10, 0) = error("DIVISION_BY_ZERO")

import ballerina/io;

// returns a union type of

// int - in case of success

// error - in case of failure

function div(int a, int b) returns int|error {

   if b == 0 {

       return error("DIVISION_BY_ZERO"); // error value

   }

   return a / b;

}

// using panic is not recommended for program related errors

// here it is just used for demonstration of a language construct

function business_process(int a, int b) returns int {

   // checkpanic expression checks the value of the subexpression and

   // panics (starts stack unwinding) if the result is a type of error

   // so checkpanic is a short-hand for the following pseudocode

   // int c = if subexpression value is error {

   //     panic subexpression; // an error

   // } else {

   //     subexpression value; // an int

   // }

   int c = checkpanic div(a, b); // "throws" if div() returns error

   c = c + 1; // this line is executed only if div() returns int

   return c;

}

public function main() {

   // type guards are not needed because return type of

   // business_process() is int only but non-trapped panics

   // will terminate the program

   int c1 = business_process(10, 2);

   io:println(string`business_process(10, 2) = ${c1}`);

   // trapping a panic stops stack unwinding and gives

   // access to the associated error object

   int|error c2 = trap business_process(10, 0);

   if c2 is error {

       io:println(string`business_process(10, 0) = ${c2.toString()}`);

   } else {

       io:println(string`business_process(10, 0) = ${c2}`);

   }

Summary 

Advent of Code is a wonderful set of software puzzles – no doubt. I’m not sure if I have gained any measurable professional benefit by resolving the puzzles but at least I need to think different than in my day job. And I’ll be there again in December 2021 for sure!

Swan Lake is the second stable major release of the Ballerina cloud native programming language. Albeit a large and complex language one of Ballerina’s major design goal is to be easily learnable for a casual user. The second release will provide a huge number of new features over the first release and the language has a great potential for integration and API-related use scenarios. Thus you can find it on our tech radar, too.

Does continuous learning while having fun sound good for you? Check out our open positions below: