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

More Functional Patterns: Higher-order functions and guards

Continuing our foray into the More Functional Patterns chapter, today we're looking at higher-order functions and guards.

Higher-order functions

First off, let's establish some common ground and agree on the definition. According to Wikipedia and the Haskell wiki, a higher-order function is a function that takes other functions as arguments or returns a function as a result. In essence, it's a function that operates on functions as values. Because functions are just any other value in haskell1, this comes very naturally to the language.

So to qualify as a higher-order function, a function must fulfill one of two criteria:

Accept a function as a parameter

map and sorting functions are great examples of this. For instance, map's signature is map :: (a -> b) -> [a] -> [b], where the first parameter is the function used to transform the values of type a in the list to values of type b. Sorting functions could take a comparison function (a -> a -> Ordering) and a list ([a]), sort the list for you and return the sorted list.

Return a function

This probably happens more than you realize. Any function that accepts more than one argument in Haskell is by default a higher-order function due to how the lambda calculus works. That said, you can also explicitly return a partially applied function based on the input arguments. For instance, this example takes a boolean value saying whether the function should return a partially applied multiplication or addition, and the first argument to apply to the calculation. It returns the partially applied function:

       f :: Num a => Bool -> a -> (a -> a)
       f multiply n =
         if multiply then
           (n*)
         else
           (n+)

It's a silly example, but it gets the point across.

Guards

Next up, the book returns to a form of pattern matching by introducing guards, which allow us to run code conditionally based on the truth of a statement. If you think that sounds a lot like an if-then-else expression, that's because it works pretty much the same but with some different syntax. We'll run through a couple of different examples to examine the various features that are introduced.

The basic syntax looks like this:

    myAbs :: Integer -> Integer
    myAbs x
      | x < 0     = (-x)
      | otherwise =   x

This function has two guards, each beginning with a pipe symbol (|). Note that we don't need an equals sign after the arguments in the first line of the definition; instead, the symbol comes after each respective guard. The first of the guards that evaluates to True will be executed.

In this function, we have two cases: One for when the value of x is negative (in which case we'll return the absolute value of x), and one for every other case. In the above example, we have used the word otherwise, which is a synonym for True, as a catch-all for every other case.

If we want to explicitly enumerate all options, we can do that too:

    myAbs :: Integer -> Integer
    myAbs x
      | x <  0 = (-x)
      | x >= 0 =   x

This will work the exact same as the previous version, but in this case we're handling all cases explicitly. When explicitly listing all options, make sure you have enabled warnings (or errors) for non-exhaustive patterns to avoid accidentally partial functions.

What if we want to share some values between the different guards? We can use where-statements for that! Imagine a function that takes the lengths of the two legs (catheti) of a right triangle and returns whether the triangle is big, small, or medium based on the length of the hypotenuse:

    triangleSize :: (Floating a, Ord a) => a -> a -> String
    triangleSize c1 c2
      | h >= 5 = "Big triangle"
      | h >= 2 = "Medium triangle"
      | h <  2 = "Small triangle"
      where h = sqrt (c1^2 + c2^2)

When declaring variables like this, they're in scope for all of the guards.

This last example also demonstrates the importance of order on the guards. Because only the first guard that evaluates to True is executed, the above function works as expected. If we switched the two guards for big and medium triangles, no triangle would ever be considered 'big'. What a sad world that would be.

Next up

That was all we had time for today, my dear reader. We still have a little bit left of this chapter: function composition and pointfree style. Two very interesting topics which I'm looking forward to covering next time. Until then: take care!

Footnotes

1You may have heard the term 'first-class functions'. This is what that means. You can pass functions around like any other variable, store them in data structures, assign them to variables, and so forth. See MDN or Wikipedia for more.


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

More Functional Patterns: Pattern Matching

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:

      isTheAnswer :: Integer -> Bool
      isTheAnswer 42 = True
      isTheAnswer _ = False

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

        listState :: [a] -> String
        listState [] = "The list is empty"
        listState [_] = "The list has one element"
        listState (_:_:_) = "The list has at least two 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:

        data User = LoggedIn String | Anonymous

        userInfo :: User -> String
        userInfo Anonymous = "The user is anonymous"
        userInfo (LoggedIn "admin") = "The user is an administrator"
        userInfo (LoggedIn username) = "The user is " ++ username

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:

    listState :: [a] -> String
    listState xs =
      case xs of
        [] -> "The list is empty"
        [_] -> "The list has one element"
        (_:_:_) -> "The list has at least two elements"

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 "async/.await" printed in the distance. There are a set of glowing eyes in the background and a shadow crab with glowing eyes on the right.

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:

  [package]
  name = "async-basics"
  version = "0.1.0"
  authors = ["Your Name <your.email@provider.tld>"]
  edition = "2018"

  [dependencies]
  async-std = { version = "1", features = ["attributes"] }
  surf = "1"

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:

  use surf;

  #[async_std::main]
  async fn main() {
      match surf::get("https://pokeapi.co/api/v2/move/surf").recv_string().await {
          Ok(s) => println!("Fetched results: {:#?}", s),
          Err(e) => println!("Got an error: {:?}", e),
      };
  }

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