DEV Community

Cover image for Giving Ballerina Lang a Twirl
Kevin
Kevin

Posted on • Originally published at kevinquinn.fun

Giving Ballerina Lang a Twirl

I stumbled onto Ballerina a few months back during a random jaunt through the interwebs, and after digging through their site I was intrigued enough by their features & guiding principles to give it an expanded 'Hello World!' test. When I try a new language, I want to get past the toy examples and see what it's actually like to develop. My current method is to build a very simple package based on a popular Open Source module from PyPi or NPM. This can be a handy learning strategy, as you don't have to spend time thinking about what to build or features, you can just copy features directly from existing implementations and focus entirely on learning to put it together. You also go through the full cycle of building, testing, and deploying a module where others can use it, imitating a real world use-case. I spent about an hour reading through docs & examples so I have a rough idea where I can find answers when I get stuck, but I learn best from action, so it was time for the rubber to meet the road. For people who can't resist spoilers, here's the module I created during this process: thyme, and docs!. If you're interested in reading the suggestions I compiled to improve the developer experience & adoption of Ballerina, you can find it here.

What is Ballerina

Ballerina is a young language in development by the company WSO2, and is advertised as being "Developer First" and to have built-in functionality so developers have to spend less time worrying about how you can get your code deployed to cloud providers and many of the pieces of building maintainable & reliable applications like documentation & testing. As they say "We plumb, you build!". I've probably butchered their marketing spiel, so to any WSO2 employees reading this, my bad. For the rest of you, go check out their landing page to see if Ballerina is a good fit for you.

Getting Started

I don't have much to say on getting up and running, so I'll leave it to their documentation on how to install the language and how to use the Ballerina CLI tools. The Quick Tour will get you the rest of the way with a slightly more advanced 'Hello World' to ensure things are working correctly before you dive deeper.

Writing a simple module

Kicking things off, I decided Dotenv would be a good starting point. Dotenv loads configuration information from a .env file into your process as environment variables. A simple concept, but it touches on useful topics to know in any language, like interacting with the environment your application runs on. I started off right away with writing tests and found it quite helpful, as it has forced me to not only learn how tests work in Ballerina but also tackle a variety of utility tasks you need when writing tests, like generating random numbers, handling errors, working with strings & arrays, and basic flow control. Handling errors is done explicitly, though initially it appeared you could cop out by adding check before the operation, e.g. int val = check iThrowErrors(). I was wrong.

Things went downhill quickly after writing some initial tests. Ballerina currently runs on the JVM and uses hooks into Java to handle some of it's standard library functionality. From other languages I've used I assumed there would be easy access to environment variables, but there is no setenv capability in the underlying Java package java.lang.System. This Java Dotenv package mentions the same issue, so hopefully I'm not missing something obvious. You could implement a rough equivalent by loading from a file into an object, but the Ballerina Config API seems like a well thought out implementation along those lines, and making a crappier version of it didn't sound all that interesting.

The lesson here? Check the most important functionality of your idea is possible before doing any of the other work. Luckily this is just for learning, so the time already spent on language features wasn't wasted. That said, a pivot was required.

Pivoting

I am inspired by projects which focus on creating a great developer experience, like Python's Requests module, "HTTP for Humans". Just because you're writing code doesn't mean it has to be a pain to do common tasks like making web requests or parsing data. Datetime is often one of these pain point. Trying to get your variables in the right format, the right timezone, and doing operations with them sucks without good libraries. Looking over Ballerina's Time module, it has useful pieces but it's missing the friendly functions which make people enjoy coding. Developers want to build their projects, not the minutiae like "finding diff between two times". Alright, so building a developer-focused time library will be the goal. Inspiration will come from Momentjs, a hugely popular Javascript library which simplifies the sucky parts of date & time.

Writing a (new) simple module & what I learned

Though the main purpose is learning, I like to have at least a semblance of a goal to drive what I'm doing. The philosophy I took was to wrap and use the native classes as much as possible, making it easy to plug in, while abstracting the annoying bits of date time interaction so developers can get back to building their widgets. For this reason, I decided to build mostly module methods that accept & return the native time:Time objects. I could have focused on only creating functions around my custom classes, but that makes it harder for someone stumbling onto your project to adopt it as they have to go all in on your implementation.

On to the experience of actually implementing. Error handling confused me for a good bit, I may just be dense but it tripped me up time and again even after reading the documentation. Their test suite examples could use some work, it doesn't help me to know if you can print something before each function. I want to know how to do test setup and then be able to access those variables, it helps keep a codebase clean & simplify your test cases. I found myself searching through the repo a lot to answer my questions. I also attempted to use fixtures for tests, but as far as I can tell you have to have module global variables to assign to if you want to access anything generated in fixtures. Seems like a pain, maybe there is a feature of Ballerina I missed which passes context between. A few more small thoughts & concepts I ran into:

  • Their use of union types is quite handy. It's convenient to be able to do unions when you want to expect multiple things, like it should be an int, but occasionally is an error, well then return int|error. It works even for something wild like [string, int, int|error].
  • For their logging module, it would be super nice to have something a little cleaner than having to use io.sprintf everywhere to log variables. Not a huge issue, but really clutters up the space.
  • Ran into a bit of trouble with assigning output to an existing variable when the function returns an error. From what I found, cop out way is to create a new variable, check the error, then assign it to your desired.
  • I struggled while writing tests trying to understand what the best practice should be for handling errors that happen during tests. The documentation was fairly sparse. Also a nitpicky thing, but the failures from tests should show at the bottom. It's frustrating to have to scroll through all the passing tests to find out what broke.
  • Another thing I found while writing tests, thought not necessarily to do with Ballerina, is it's difficult to write only a single example test case after having been introduced to property-based testing with Python's Hypothesis. It makes you want to fuzz the crap out of all your code.
  • Learn the any aspect, it's powerful for dealing with maps.
  • If you want to use built-ins like int, you have to use the special ' syntax when importing: import ballerina/lang.'int;.
  • It seems you can't use list of tuples or the any type when trying to use dataProvider. In docs, it says "The given Ballerina function should return an array of arrays (e.g., string[][] for a test function that accepts string parameters). Each array of the returned array of arrays should have a length similar to the number of arguments of the function". Looks like it doesn't support any though. It would be super useful to be able to do this. Json is available as an option in their docs, which could be close, but it seems like overkill if you just want something simple like a string:int pair.
  • It kept biting me in the butt to not know an easy way to go from int to string. I wish I could have found that early on.

At this point, I had a decent chunk of a half-way decently tested module, and a lot of ideas about the language. There are a lot of improvements this module could undergo, but a short list would be better repo file structure, using logging correctly instead of println() , better error handling within tests & adding more property-based tests, and adding more helper functions. It also could have used more of the fancy features available in Ballerina, like Type guards, Elvis operators, custom error types, binding patterns, and more.

Outcome & thoughts on the language

After a few hours, I had successfully completed a not totally terrible module and published it to Ballerina Central. The final product is available at central.ballerina.io/i_dont_remember/thyme. I really liked the built in support for a code coverage report as well as letting you write documentation inline. Anything to reduce the friction it takes to get more documentation for a codebase is a plus in my book. I had no idea what I was doing and was still able to generate useful documentation to go along with my module. At first I was not a fan of the error handling system (I have to explicitly handle this again?!?!?), but it grew on me. Now I like it forces the developer to think through error situations and handle them, rather than hand-waving it away and hoping it doesn't explode later. The Ballerina team wrote a 3 part series diving deeper into error handling if you're on your own learning journey. On a personal note, I think my approach to writing in Ballerina is heavily object-oriented (OOP). I tend to think in classes & objects, which might be at odds with how languages like Ballerina, Go, and others operate best. This is something I will need to dive deeper into to expand my personal skillset, as it would be good to understand how to switch thinking for languages that aren't necessarily OOP. Adding a useful article on inheritance in Ballerina. Of all the decisions the Ballerina team has made, it seems like the most valuable is their easy integration with Java libraries with the bindgen tool. This opens the massive Java ecosystem for use, which I'm sure will help them in getting companies to adopt knowing they don't have to throw out everything they've built.

It was interesting to give Ballerina a spin, though my personal goals will keep me with other languages for now. I have a strong feeling it will continue growing over the next few years. With a company backing it, WSO2, they have a strong base to keep it going through the early stages when a language is most likely to lose steam, so I wish them the best of luck with getting it off the ground and into mainstream production use. I also wrote up opportunities I noticed for Ballerina to tweak their developer experience & marketing to grow the community around the language. If you'd like to see my thoughts on what Ballerina can do to become a widely used production language, you can find it here.

Photo by ALEXANDRE DINAUT on Unsplash

Top comments (0)