Let's read: Haskell Programming from First Principles, pt VIIa

More Functional Patterns: Pattern Matching
Excerpt from the Programming Haskell From First Principles book cover, showing just the title. There is an overlay saying 'Chapter 7a' across the top right corner.

Woah, I have to move the text now too?

We’ve reached chapter 7: More Functional Patterns. This is a pretty big chapter that covers a lot of ground, so to make it more digestible to you (and more manageable to me), I’m going to break this chapter up into multiple pieces. In this post, we’ll be looking at pattern matching and case expressions. Later posts will cover higher-order functions, function composition, and pointfree style, so there’s lots to look forward to!

Pattern matching

“Pattern matching is an integral and ubiquitous feature of Haskell”. Thus opens the sub-chapter on pattern matching. If you’ve seen any amount of Haskell code, you’ve probably come across it, but in case you haven’t (and if you have: just to make sure we’re on the same page), here’s a primer.

Pattern matching is a way for us to match values against certain ‘patterns’. Depending on the context, a pattern can be a wide range of things, including specific strings, numeric literals and list syntax; pattern matching can match on any and all data constructors. Pattern matching can even let us match on the inner structure of the thing we’re matching on, such as a list or a tuple. Let’s have some examples.

We can check whether a provided Integer is of a specific value, like in the following case:

We can check whether a list contains zero, one, or many elements:

We can also check which data constructor has been used to create a value and extract the constructor parameters from it or even check them for specific values:

This covers some of the patterns you might see used with pattern matching. Keep in mind that the order of the patterns matter: they are evaluated from top to bottom, and once a pattern matches, no more cases will get checked.

Covering all the cases

When pattern matching, you should always handle all cases to avoid partial functions. This might seem like a hassle, but you don’t need to handle all the cases explicitly. As seen in the first example (isTheAnswer), the _ pattern works as a catch-all, meaning that if no other patterns have matched yet, this will match anything.

In addition to just being vigilant about matching all possible combinations, you can also turn up the compiler’s crankiness by using the -Wall flag. This will give you warnings if your patterns are non-exhaustive.

Case expressions (or: more pattern matching)

Similarly to the pattern matching above, we also have case expressions in Haskell. They work the same way as basic pattern matching, but the syntax is a bit different. In short, it gives you all the power that you get from pattern matching, but with a bit more syntax. Let’s rewrite the listState function above using a case expression to see the similarities:

This looks very much like pattern matching (and serves the same purpose in this case), except that we use the case ... of structure to match on a named variable instead of directly on an input. Which is more appropriate depends on your use case and preferences.


And that’s it for today, kids. A shorter, more digestible format that covers only the most essential. I’ll cover the rest of the chapter in this way, and then we’ll see what works best moving forward. Next time: higher-order functions! Until then: take care!


Async Rust: async main

Improving async ergonomics
An 80's style cyberscape with the words

New and improved.

In the previous post we had an introductory glance at async/.await in Rust, looking at what it does and how you’d use it. However, the little bit of code we were left with at the end still felt a bit rough. Coming from languages where you can have an async main function (or at least one that lets you act as if it is), it felt like an extra hurdle to have to extract all async functionality out into a separate function.

Luckily, Reddit user mbuesing pointed out that there is an attribute available in async-std that you can use to make your main function asynchronous: async_std::main! Let’s have a look at what changes we’d have to make to incorporate that.

First off, let’s update the Cargo.toml file. It’s mostly the same as last time, but we’re going to have to add the “attributes” feature from async-std:

Next up, let’s update the main.rs file. If we’re doing everything within the main function, we can cut it down to about 8 lines of code, compared to the 14 lines we had last time, and because it’s such a simple program, it’s not any less readable:


So there you have it. With some extra attributes, we can make async code pretty ergonomic in Rust. Now, let’s make something cool!


Async Rust

A gentle introduction
A group of black crabs walking towards the words

Hot off the heels of RustFest Barcelona and the stabilization of async/.await, I think it’s safe to say that one of Rust’s most anticipated language features has finally landed. And for that occasion (and because I’ve had some trouble understanding certain bits of it myself), I wanted to write a little introduction to asynchronous programming in Rust. We’ll be creating a super simple application that fetches some data from the internet using the our newfound async abilities. The stabilization of async/.await also coincides nicely with another event I’m excited about: This week saw the release of the most recent entries in the mainline Pokémon games, Pokémon Sword and Shield, and because I’m a bit too busy to pick them up just yet, I’ll make do by fetching data from the PokéAPI for now.

I’m assuming some base knowledge of Rust’s syntax and ecosystem, but I hope that this is pretty accessible even to people very new to the community.

But before diving into the coding part, let’s cover some basic concepts of asynchronous programming and how it might be a bit different in Rust than what you’d expect.

What does async mean?

In Rust, when we talk about async, we’re talking about running code concurrently, or having multiple overlapping (in time) computations run on a single thread. Multithreading is a related, but distinct concept. Multithreading is ideal for when you’ve got computationally intensive tasks (so-called CPU-bound tasks) that can be spread across multiple, separated cores. Concurrent programming is better suited for when the task spends a lot of time waiting, such as for a response from a server. These tasks are called IO-bound.

So asynchronous programming lets us run multiple of these IO-bound computations at the same time on a single thread. They can run at the same time because when they’re waiting for a response, they’re just idle, so we can let the computer keep working on something that isn’t waiting. When we reach a point where we need the result of an asynchronous computation, we must .await it. In Rust, values that are ‘awaitable’ are known as ‘futures’.

Rusty weirdness

async in Rust may be a bit different from what you’re used to in other languages. Having done asynchronous coding mostly in JavaScript and C#, it certainly was to me. Here’s a few key things to understand:

An async function does not (necessarily) start executing immediately

To start an asynchronous function, you must either .await it or launch a task using an executor (we’ll get to that in a moment). Until this happens, all you have is a Future that has not started. Let’s look at an example to make it clearer:

So in the above little code snippet, here’s what’s going on.

  • The first line imports async_std::task. There’s more on this below, but we need an external library to run futures as the standard library does not come with an executor.
  • The async function negate_async takes as input a signed integer, sleeps for 5 seconds, and returns the negated version of that integer.
  • The async function f is more interesting:
    • The first line (let neg ...) creates a Future of the negate_async function and assigns it to the neg variable. Importantly, it does not start executing yet.
    • The next line of code (let neg_task ...) uses the task::spawn function to start executing the Future returned by negate_async. Like with neg, the Future returned by negate_async is assigned to the neg_task variable.
    • Next: we sleep for a second. This is so that it will be obvious from the output when a task starts running.
    • Finally, we await both futures, add them together, and return them. By awaiting neg, we start executing the Future and run it to completion. Since neg_task has already been started, we just wait for it to finish.

    So what’s the result of this, then?

    As we can see, the second future, neg_task, started executing as soon as it was called—thanks to task::spawn—while neg did not start executing until it was awaited.

You need an external library to use async/.await

As was briefly alluded to above, you need to reach for an external library to do asynchronous programming in Rust. This took me a while to understand, as I’m used to it being part of the language experience. In Rust, however, you need a dedicated executor1. The executor is what takes care of executing the futures, polling them and returning the results when they’re done. The standard library does not come with an executor, so we need to reach out to an external crate for this. There are a few ones to choose from, but the two most prominent ones are async-std (which we’re using here) and tokio.

A minimal async example!

Alright, let’s get practical. This is the reason that I’m writing this post. As mentioned at the start, we’ll be creating a super simple application that fetches some Pokémon data and prints it to the console. For preparation, make sure you’ve got at least version 1.39 of Rust and cargo available.

Step 1: creating the application

Let’s create a new application! Simply run this command in your preferred directory:

Step 2: Dependencies

We’re going to be using async-std for spawning tasks, and surf to fetch data from the API. Let’s add them to the Cargo.toml file. Your whole file should look something like this:

Nice! This is going swimmingly!

Step 3: Fetch data

Okay, final step. Let’s modify the main.rs file. We’ll make it as simple as possible. Here’s what we want to use:

That’s all the code you need. In fact, it’s more than what you need, as some parts have been broken up for legibility. Let’s walk through it!

use statements
Nothing exciting here. Just importing the crates we declared in the Cargo.toml file: surf and async_std.
fetch
This is simply a thin wrapper around the surf::get function which returns either the payload as a String or an Exception if something went wrong.
execute
This function calls fetch with the endpoint for the move Surf, waits for the result to return, and then matches on the result. If everything went well: print the output. Else: print the error.
main
main simply kicks off execute and waits for it to finish. task::block_on is a synchronous counterpart to task::spawn that starts an asynchronous operation, but blocks until it has finished. Because the main function can’t itself be async (at least not at the time of writing), we can’t use .await in it, but we can block on asynchronous operations.

Step 4: Extend it!

Hey, you made it this far; congrats! That’s all I really have in store for you for this one, but if you want to play around a bit more, how about adding serde and try using surf’s recv_json<T> instead? If you’d rather keep looking at async/.await, how about performing multiple requests simultaneously? Or how about making a PokéAPI CLI? (Ooh, that sounds like fun! Hit me up if you’re doing this; I want in!)

Parting words and resources

So there you have it, dear reader. I hope you have found this useful. async/.await is finally stabilized and it feels like we’ve taken a major leap forward. I’m very much looking forward to seeing what happens in the coming months and what the community makes of this.

If you’re looking for more resources on async Rust, be sure to check out the Async Book. I also recommend the async-std book for some extra insights.

Until next time: take care!

Footnotes


  1. See this insightful Reddit comment thread for more on this.

First Prev Page 1 Next Last