James Bowen James Bowen

Beginners Series Updated!

newlogo3transparent.png

Where has Monday Morning Haskell been? Well, to ring in 2021, we've been making some big improvements to the permanent content on the site. So far we've focused on the Beginners section of the site. All the series here are updated with improved code blocks and syntax highlighting. In addition, we've fully revised most of them and added companion Github repositories so you can follow along!

Liftoff

Our Liftoff Series is our first stop for Haskell beginners. If you've never written a line of Haskell in your life but want to learn, this is the place to start! You can follow along with all the code in the series by using this Github repository.

Monads Series

Monads are a big "barrier" topic in Haskell. They don't really exist much in most other languages, but they're super important in Haskell. Our Monads Series breaks them down, starting with simpler functional structures so you can understand more easily! The code for this series can be found on Github here.

Testing Basics

You can't do serious production development in any language until you've mastered the basics of unit testing. Our Testing Series will school you on the basics of writing and running your first unit tests in Haskell. It'll also teach you about profiling your code so you can see improvements in its runtime! And you can follow along with the code right here on Github!

Haskell Data Basics

Haskell's data types are one of the first things that made me enjoy Haskell more than other languages. In this series we explore the ins and outs of Haskell's data declaration syntax and related topics like typeclasses. We compare it side-by-side with other languages and see how much easier it is to express certain concepts! Take a look at the code here!

What's Next?

Next up we'll be going through the same process for some of our more advanced series. So in the next couple weeks you can look forward to improvements there! Stay tuned!

Read More
James Bowen James Bowen

Countdown to 2021!

fireworks.jpg

At last. 2020 is nearly over. It's been a tumultuous year for the entire world, and I think most of us are glad to be turning over a new page, even if the future is still uncertain. As I always do, I'll sign off the year with a review of the different concepts we've looked at this year, and give a preview of what to expect in 2021.

2020 In Review

There were three major themes we covered this year. For much of the start of this year, we focused on AI. The main product of this was our work on a Haskell version of Open AI Gym. We explored ways to generalize the idea of an AI agent, including cool integrations of Haskell ideas like type families. We even wrote this in such a way that we could incorporate Tensor Flow! You can read about that work in our Open AI Series.

Over the summer, we switched gears a bit and focused on Rust. In our Rust Web Series we solved some more interesting problems to parallel our Real World Haskell Series. This included building a simple web server and connecting to a database.

Then our final work area was on our Haskellings program. Modeled after Rustlings, this is intended to be an automated beginner tutorial for the Haskell language. For the first time, I changed up the content a bit and did a video series, rather than a written blog series. So you can find the videos for that series on our YouTube Channel!

We're looking for people to contribute exercises (and possibly other code) to the Haskellings project, so definitely visit the repository if you'd like to help!

Looking Forward

There will be some big changes to the blog in 2021. Here are some of the highlights I'm looking forward to:

Spending more time on how Haskell fits into the broader programming ecosystem and what role it can play for those new to the industry. What can beginning programmers learn from the Haskell language and toolchain? What lessons of Haskell are applicable across many different languages? More exploration of different content types and media. As mentioned above, I spent the last part of 2020 experimenting with video blogs. I expect to do more of this type of experimenting this year. Upgrading the site's appearance and organization. Things have been pretty stagnant for a while, and there are a lot of improvements I'd like to make. For one example, I'd like to make coding sections more clear and interactive in blog posts. New, lighter-weight course material. Right now, our course page has 2 rather large courses. This year I'm going to look at breaking the material in these out into smaller, more manageable chunks, as well as adding a couple totally new course offerings at this smaller size.

I've set a lot of these goals before and fallen short. Unfortunately, I've found that these priorities often get pushed aside due to my desire to publish new content weekly, as I've been doing for over 4 years now (how time flies!). But starting in 2021, I'm going to focus on quality over quantity. I do not plan on publishing every week, and a lot of the blogs I do publish will highlight improvements to old content, rather than being new, detailed technical tutorials. I hope these changes will take the most important content on the blog and make it much more useful to the intended audiences.

I also have a tendency of creating projects to demonstrate concepts, but leave the projects behind once I am done writing about those concepts. This year, I hope to take a couple of my projects, specifically Open AI Gym and the Haskellings Beginner Tutorial and turn them into polished products that other developers will want to use. This will take a lot of focused time and effort, but I think it will be worth it.

So even though you might not see a new post every Monday, never fear! Monday Morning Haskell is here to stay! I hope all of you have a happy and safe new year!

Read More
James Bowen James Bowen

Open Sourcing Haskellings!

newlogo3 (3).png

In the last couple months we've been working on "Haskellings", an automated Haskell tutorial inspired by Rustlings. This week, I'm happy to announce that this project is now open source! You can find the (very early) version here on Github. I'll be working on making the project more complete throughout 2021, but I would really value any contributions the community has to this project! In this article, I'll list a few specific areas that would be good to work on!

More Exercises

The first and most important thing is that we need more exercises! I've done a couple simple examples to get started, but I'd like to crowd-source the creation of exercises. You can use the set of Rustlings exercises as some sort of inspiration. The most important topics to start out with would be things that explain the Haskell type system, so the different sorts of types, expressions and functions, as well as making our own data types. Other good concepts include things like syntax elements (think "where" and "case") and type classes.

Operating System Compatibility

I've definitely cut a few corners when it comes to the MVP of this project. I've only been working on Linux, so it's quite possible that there are some Linux-specific assumptions in the file-system level code. There will need to be some testing of the application on Windows and Mac platforms, and some adjustments will likely be necessary.

GHC Precision

Another area that will need some attention is the configuration section. Is there a cleaner way to determine where the GHC executable lives? What about finding the package database that corresponds to our Stack snapshot? My knowledge of Stack and package systems is limited, so it's very likely that there are some edge cases where the logic doesn't work out.

Exercise Cleanup

Right now, we list all the exercises explicitly in the ExerciseList module. But they're listed in two different places in the file. It would be good to clean this up, and potentially even add a feature for automated detection of exercise features. For example, we can figure out the filename, the directory, and whether or not it's runnable just by examining the file at its path! Right now the only thing that would need to be specified in "code" would be the order of exercises and their hints.

Contributing

If you're interested in contributing to this project, you can fork the repository, put up a pull request, and email me at james@mondaymorninghaskell.me! I'll be working on this periodically throughout 2021, hoping to have a more complete version to publish by the end.

Read More
James Bowen James Bowen

Dependencies and Package Databases

Here's the final video in our Haskellings series! We'll figure out how to add dependencies to our exercises. This is a bit trickier than it looks, since we're running GHC outside of the Stack context. But with a little intuition, we can find where Stack is storing its package database and use that when running the exercise!

Next week, we'll do a quick summary of our work on this program, and see what the future holds for it!

Read More
James Bowen James Bowen

Adding Hints

If a user is struggling with a particular exercise, we don't want them to get stuck! In this week's video, we'll see how to add "hints" to the Watcher. This way, a user can get a little extra help when they need it! We can also use this functionality to add more features in the future, like skipping an exercise, or even asking the user questions as part of the exercise!

Read More
James Bowen James Bowen

Executing Executables!

In Haskell, we'd like to think "If it compiles, it works!" But of course this isn't generally the case. So in addition to testing whether or not our exercises compile, we'll also want to run the code the user wrote and see if it works properly! In this week's video, we'll see how we can distinguish between these two different exercise types!

Read More
James Bowen James Bowen

Testing the Watcher

In our Haskellings program, the Watcher is a complex piece of functionality. It has to track changes to particular files, re-compile them on the spot, and keep track of the order in which these exercises should be done. So naturally, writing unit tests for it presents challenges as well! In this video, we'll explore how to simulate file modifications in the middle of a unit test.

Read More
James Bowen James Bowen

Unit Testing Compilation

We've used Hspec before on the blog. But now we'll apply it to our Haskellings program, in conjunction with the program configuration changes we made last week! We'll write a couple simple unit tests that will test the basic compilation behavior of our program. Check out the video to see how!

Read More
James Bowen James Bowen

Using the Handle Abstraction

Our haskellings program is starting to get a bit more complicated, so it would be nice to write some unit tests for it. But this is very difficult to do for a command line application! To set this up, we'll start by refactoring our program to use the Handle abstraction. This allows the program to work regardless of whether it's using the command line or file for its input. So the user will see the command line version, but our tests will be able to use files! We'll also use the process of Compile Driven Development to systematically refactor our program to use these new configuration elements!

Read More
James Bowen James Bowen

Sequencing Exercises in the Watcher

Our watcher program can keep tabs on our exercise files, but it doesn't have any notion of an exercise order. In this week's video, we'll fix this! Our watcher will focus on one specific "current" exercise. When we're done with that, it will move us on to the next one automatically! This video involves a good example of using recursion in our Haskell workflow. But even better, we'll see a rudimentary usage of an MVar to pass information between the threads of our program. Take a look!

Read More
James Bowen James Bowen

Overriding Process Handlings and Terminal Colors

Right now, our Haskellings program doesn't give the user any information or output aside from the actual compilation stream. For this week's video blog, we'll dig in a bit deeper to the process to give the user better information. We'll use the process's exit information and override its handles to control which output the user sees and when. Then as a final flourish, we'll color the user's terminal output result to be more helpful!

Read More
James Bowen James Bowen

Watching Files with FS-Notify!

This week we continue laying out the groundwork for our "Haskellings" project. A key part of the this program is the automated aspect of "watch" mode. A user should be able to modify their file and then immediately see the results of their changes in the watcher window. This week, we get familiar with the fsnotify package, which lets us watch files and directories and take actions in our program when they change!

Read More
James Bowen James Bowen

Haskellings 2: Better Configuration

This week we'll continue working on our nascent Haskellings project. There's a few interesting things we'll learn here around using the directory package. We'll also explore how to use the Seq data structure to implement a quick Bread-First-Search algorithm!

Read More
James Bowen James Bowen

Starting Haskellings!

After learning about the Rustlings program in the last few weeks, we're now going to try to start replicating it in Haskell! In this week's video blog, we'll learn a little bit about using the ghc command on its own outside of Stack/Cabal, and then how to run it from within our program using the System.Process library.

Read More
James Bowen James Bowen

Rustlings Part 2

This week we continue with another Rustlings video tutorial! We'll tackle some more advanced concepts like move semantics, traits, and generics! Next week, we'll start considering how we might build a similar program to teach beginners about Haskell!

Read More
James Bowen James Bowen

Rustlings Video Blog!

We're doing something very new this week. Instead of doing a code writeup, I've actually made . In keeping with the last couple months on content, this first one is still Rust related. We'll walkthrough the Rustlings tool, which is an interactive program that teaches you the basics of the Rust Language! Soon, we'll start exploring how we might do this in Haskell!

You can also watch this video on our YouTube Channel! Subscribe there or sign up for our mailing list!

Read More
James Bowen James Bowen

Rust Web Series Complete!

rust_web.jpg

We're taking a quick breather this week from new content for an announcement. Our recently concluded Rust Web series now has a permanent spot on the advanced page of our website. You can take a look at the series page here! Here's a quick summary of the series:

  1. Part 1: Postgres - In the first part, we learn about a basic library to enable integration with a Postgresql Database.
  2. Part 2: Diesel - Next up, we get a little more formal with our database mechanics. We use the Diesel library to provide a schema for our database application.
  3. Part 3: Rocket - In part 3, we take the next step and start making a web server! We'll learn the basics of the Rocket server library!
  4. Part 4: CRUD Server - What do we do once we have a database and server library? Combine them of course! In this part, we'll make a CRUD server that can access our database elements using Diesel and Rocket.
  5. Part 5: Authentication - If your server will actually serve real users, you'll need authentication at some point. We'll see the different mechanisms we can use with Rocket for securing our endpoints.
  6. Part 6: Front-end Templating - If you're serving a full front-end web app, you'll need some way to customize the HTML. In the last part of the series, we'll see how Rocket makes this easy!

The best part is that you can find all the code for the series on our Github Repo! So be sure to take a look there. And if you're still new to Rust, you can also get your feet wet first with our Beginners Series.

In other exciting news, we'll be trying a completely new kind of content in the next couple weeks. I've written a bit in the past about using different IDEs like Atom and IntelliJ to write Haskell. I'd like to revisit these ideas to give a clearer idea of how to make our lives easier when writing code. But instead of writing articles, I'll be making a few videos to showcase how these work! I hope that a visual display of the IDEs will help make the content more clear.

Read More
James Bowen James Bowen

Unit Tests and Benchmarks in Rust

hourglass.jpg

For a couple months now, we've focused on some specific libraries you can use in Rust for web development. But we shouldn't lose sight of some other core language skills and mechanics. Whenever you write code, you should be able to show first that it works, and second that it works efficiently. If you're going to build a larger Rust app, you should also know a bit about unit testing and benchmarking. This week, we'll take a couple simple sorting algorithms as our examples to learn these skills.

As always, you can take a look at the code for this article on our Github Repo for the series. You can find this week's code specifically in sorters.rs! For a more basic introduction to Rust, be sure to check out our Rust Beginners Series!

Insertion Sort

We'll start out this article by implementing insertion sort. This is one of the simpler sorting algorithms, which is rather inefficient. We'll perform this sort "in place". This means our function won't return a value. Rather, we'll pass a mutable reference to our vector so we can manipulate its items. To help out, we'll also define a swap function to change two elements around that same reference:

pub fn swap(numbers: &mut Vec<i32>, i: usize, j: usize) {
    let temp = numbers[i];
    numbers[i] = numbers[j];
    numbers[j] = temp;
}

pub fn insertion_sorter(numbers: &mut Vec<i32>) {
    ...
}

At its core, insertion sort is a pretty simple algorithm. We maintain the invariant that the "left" part of the array is always sorted. (At the start, with only 1 element, this is clearly true). Then we loop through the array and "absorb" the next element into our sorted part. To absorb the element, we'll loop backwards through our sorted portion. Each time we find a larger element, we switch their places. When we finally encounter a smaller element, we know the left side is once again sorted.

pub fn insertion_sorter(numbers: &mut Vec<i32>) {
    for i in 1..numbers.len() {
        let mut j = i;
        while j > 0 && numbers[j-1] > numbers[j] {
            swap(numbers, j, j - 1);
            j = j - 1;
        }
    }
}

Testing

Our algorithm is simple enough. But how do we know it works? The obvious answer is to write some unit tests for it. Rust is actually a bit different from Haskell and most other languages in the canonical approach to unit tests. Most of the time, you'll make a separate test directory. But Rust encourages you to write unit tests in the same file as the function definition. We do this by having a section at the bottom of our file specifically for tests. We delineate a test function with the test macro:

[#test]
fn test_insertion_sort() {
  ...
}

To keep things simple, we'll define a random vector of 100 integers and pass it to our function. We'll use assert to verify that each number is smaller than the next one after it.

#[test]
fn test_insertion_sort() {
    let mut numbers: Vec<i32> = random_vector(100);
    insertion_sorter(&mut numbers);
    for i in 0..(numbers.len() - 1) {
        assert!(numbers[i] <= numbers[i + 1]);
    }
}

When we run the cargo test command, Cargo will automatically detect that we have a test suite in this file and run it.

running 1 test...
test sorter::test_insertion_sort ... ok

Benchmarking

So we know our code works, but how quickly does it work? When you want to check the performance of your code, you need to establish benchmarks. These are like test suites except that they're meant to give out the average time it takes to perform a task.

Just as we had a test macro for making test suites, we can use the bench macro for benchmarks. Each of these takes a mutable Bencher object as an argument. To record some code, we'll call iter on that object and pass a closure that will run our function.

#[bench]
fn bench_insertion_sort_100_ints(b: &mut Bencher) {
    b.iter(|| {
        let mut numbers: Vec<i32> = random_vector(100);
        insertion_sorter(&mut numbers)
    });
}

We can then run the benchmark with cargo bench.

running 2 tests
test sorter::test_insertion_sort ... ignored
test sorter::bench_insertion_sort_100_ints   ... bench:       6,537 ns
/iter (+/- 1,541)

So on average, it took about 6ms to sort 100 numbers. On its own, this number doesn't tell us much. But we can get a more clear idea for the runtime of our algorithm by looking at benchmarks of different sizes. Suppose we make lists of 1000 and 10000:

#[bench]
fn bench_insertion_sort_1000_ints(b: &mut Bencher) {
    b.iter(|| {
        let mut numbers: Vec<i32> = random_vector(1000);
        insertion_sorter(&mut numbers)
    });
}

#[bench]
fn bench_insertion_sort_10000_ints(b: &mut Bencher) {
    b.iter(|| {
        let mut numbers: Vec<i32> = random_vector(10000);
        insertion_sorter(&mut numbers)
    });
}

Now when we run the benchmark, we can compare the results of these different runs:

running 4 tests
test sorter::test_insertion_sort ... ignored
test sorter::bench_insertion_sort_10000_ints ... bench:  65,716,130 ns
/iter (+/- 11,193,188)
test sorter::bench_insertion_sort_1000_ints  ... bench:     612,373 ns
/iter (+/- 124,732)
test sorter::bench_insertion_sort_100_ints   ... bench:      12,032 ns
/iter (+/- 904)

We see that when we increase the problem size by a factor of 10, we increase the runtime by a factor of nearly 100! This confirms for us that our simple insertion sort has an asymptotic runtime of O(n^2), which is not very good.

Quick Sort

There are many ways to sort more efficiently! Let's try our hand at quicksort. For this algorithm, we first "partition" our array. We'll choose a pivot value, and then move all the numbers smaller than the pivot to the left of the array, and all the greater numbers to the right. The upshot is that we know our pivot element is now in the correct final spot!

Here's what the partition algorithm looks like. It works on a specific sub-segment of our vector, indicated by start and end. We initially move the pivot element to the back, and then loop through the other elements of the array. The i index tracks where our pivot will end up. Each time we encounter a smaller number, we increment it. At the very end we swap our pivot element back into its place, and return its final index.

pub fn partition(
  numbers: &mut Vec<i32>,
  start: usize,
  end: usize,
  partition: usize)
  -> usize {
    let pivot_element = numbers[partition];
    swap(numbers, partition, end - 1);
    let mut i = start;
    for j in start..(end - 1) {
        if numbers[j] < pivot_element {
            swap(numbers, i, j);
            i = i + 1;
        }
    }
    swap(numbers, i, end - 1);
    i
}

So to finish sorting, we'll set up a recursive helper that, again, functions on a sub-segment of the array. We'll choose a random element and partition by it:

pub fn quick_sorter_helper(
  numbers: &mut Vec<i32>, start: usize, end: usize) {
    if start >= end {
        return;
    }

    let mut rng = thread_rng();
    let initial_partition = rng.gen_range(start, end);
    let partition_index =
          partition(numbers, start, end, initial_partition);
    ...
}

Now that we've partitioned, all that's left to do is recursively sort each side of the partition! Our main API function will call this helper with the full size of the array.

pub fn quick_sorter_helper(
  numbers: &mut Vec<i32>, start: usize, end: usize) {
    if start >= end {
        return;
    }

    let mut rng = thread_rng();
    let initial_partition = rng.gen_range(start, end);
    let partition_index =
          partition(numbers, start, end, initial_partition);
    quick_sorter_helper(numbers, start, partition_index);
    quick_sorter_helper(numbers, partition_index + 1, end);
}

pub fn quick_sorter(numbers: &mut Vec<i32>) {
    quick_sorter_helper(numbers, 0, numbers.len());
}

Now that we've got this function, let's add tests and benchmarks for it:

#[test]
fn test_quick_sort() {
    let mut numbers: Vec<i32> = random_vector(100);
    quick_sorter(&mut numbers);
    for i in 0..(numbers.len() - 1) {
        assert!(numbers[i] <= numbers[i + 1]);
    }
}

#[bench]
fn bench_quick_sort_100_ints(b: &mut Bencher) {
    b.iter(|| {
        let mut numbers: Vec<i32> = random_vector(100);
        quick_sorter(&mut numbers)
    });
}

// Same kind of benchmarks for 1000, 10000, 100000

Then we can run our benchmarks and see our results:

running 9 tests
test sorter::test_insertion_sort ... ignored
test sorter::test_quick_sort ... ignored
test sorter::bench_insertion_sort_10000_ints ... bench:  65,130,880 ns
/iter (+/- 49,548,187)
test sorter::bench_insertion_sort_1000_ints  ... bench:     312,300 ns
/iter (+/- 243,337)
test sorter::bench_insertion_sort_100_ints   ... bench:       6,159 ns
/iter (+/- 4,139)
test sorter::bench_quick_sort_100000_ints    ... bench:  14,292,660 ns
/iter (+/- 5,815,870)
test sorter::bench_quick_sort_10000_ints     ... bench:   1,263,985 ns
/iter (+/- 622,788)
test sorter::bench_quick_sort_1000_ints      ... bench:     105,443 ns
/iter (+/- 65,812)
test sorter::bench_quick_sort_100_ints       ... bench:       9,259 ns
/iter (+/- 3,882)

Quicksort does much better on the larger values, as expected! We can discern that the times seem to only go up by a factor of around 10. It's difficult to determine that the true runtime is actually O(n log n). But we can clearly see that we're much closer to linear time!

Conclusion

That's all for this intermediate series on Rust! Next week, we'll summarize the skills we learned over the course of these couple months in Rust. Then we'll look ahead to our next series of topics, including some totally new kinds of content!

Don't forget! If you've never programmed in Rust before, our Rust Video Tutorial provides an in-depth introduction to the basics!

Read More
James Bowen James Bowen

Cleaning our Rust with Monadic Functions

clean_rust.jpg

A couple weeks ago we explored how to add authentication to a Rocket Rust server. This involved writing a from_request function that was very messy. You can see the original version of that function as an appendix at the bottom. But this week, we're going to try to improve that function! We'll explore functions like map and and_then in Rust. These can help us write cleaner code using similar ideas to functors and monads in Haskell.

For more details on this code, take a look at our Github Repo! For this article, you should look at rocket_auth_monads.rs. For a simpler introduction to Rust, take a look at our Rust Beginners Series!

Closures and Mapping

First, let's talk a bit about Rust's equivalent to fmap and functors. Suppose we have a simple option wrapper and a "doubling" function:

fn double(x: f64) -> {
  2.0 * x
}

fn main() -> () {
  let x: Option<f64> = Some(5.0);
  ...
}

We'd like to pass our x value to the double function, but it's wrapped in the Option type. A logical thing to do would be to return None if the input is None, and otherwise apply the function and re-wrap in Some. In Haskell, we describe this behavior with the Functor class. Rust's approach has some similarities and some differences.

Instead of Functor, Rust has a trait Iterable. An iterable type contains any number of items of its wrapped type. And map is one of the functions we can call on iterable types. As in Haskell, we provide a function that transforms the underlying items. Here's how we can apply our simple example with an Option:

fn main() -> () {
  let x: Option<f64> = Some(5.0);
  let y: Option<f64> = x.map(double);
}

One notable difference from Haskell is that map is a member function of the iterator type. In Haskell of course, there's no such thing as member functions, so fmap exists on its own.

In Haskell, we can use lambda expressions as arguments to higher order functions. In Rust, it's the same, but they're referred to as closures instead. The syntax is rather different as well. We capture the particular parameters within bars, and then provide a brace-delimited code-block. Here's a simple example:

fn main() -> () {
  let x: Option<f64> = Some(5.0);
  let y: Option<f64> = x.map(|x| {2.0 * x});
}

Type annotations are also possible (and sometimes necessary) when specifying the closure. Unlike Haskell, we provide these on the same line as the definition:

fn main() -> () {
  let x: Option<f64> = Some(5.0);
  let y: Option<f64> = x.map(|x: f64| -> f64 {2.0 * x});
}

And Then…

Now using map is all well and good, but our authentication example involved using the result of one effectful call in the next effect. As most Haskellers can tell you, this is a job for monads and not merely functors. We can capture some of the same effects of monads with the and_then function in Rust. This works a lot like the bind operator (>>=) in Haskell. It also takes an input function. And this function takes a pure input but produces an effectful output.

Here's how we apply it with Option. We start with a safe_square_root function that produces None when it's input is negative. Then we can take our original Option and use and_then to use the square root function.

fn safe_square_root(x: f64) -> Option<f64> {
  if x < 0.0 {
    None
  } else {
    Some(x.sqrt())
  }
}

fn main() -> () {
  let x: Option<f64> = Some(5.0);
  x.and_then(safe_square_root);
}

Converting to Outcomes

Now let's switch gears to our authentication example. Our final result type wasn't Option. Some intermediate results used this. But in the end, we wanted an Outcome. So to help us on our way, let's write a simple function to convert our options into outcomes. We'll have to provide the extra information of what the failure result should be. This is the status_error parameter.

fn option_to_outcome<R>(
  result: Option<R>,
  status_error: (Status, LoginError))
  -> Outcome<R, LoginError> {
    match result {
        Some(r) => Outcome::Success(r),
        None => Outcome::Failure(status_error)
    }
}

Now let's start our refactoring process. To begin, let's examine the retrieval of our username and password from the headers. We'll make a separate function for this. This should return an Outcome, where the success value is a tuple of two strings. We'll start by defining our failure outcome, a tuple of a status and our LoginError.

fn read_auth_from_headers(headers: &HeaderMap)
  -> Outcome<(String, String), LoginError> {
    let fail = (Status::BadRequest, LoginError::InvalidData);
    ...
}

We'll first retrieve the username out of the headers. Recall that this operation returns an Option. So we can convert it to an Outcome using our function. We can then use and_then with a closure taking the unwrapped username.

fn read_auth_from_headers(headers: &HeaderMap)
  -> Outcome<(String, String), LoginError> {
    let fail = (Status::BadRequest, LoginError::InvalidData);
    option_to_outcome(headers.get_one("username"), fail.clone())
        .and_then(|u| -> Outcome<(String, String), LoginError> {
            ...
        })
}

We can then do the same thing with the password field. When we've successfully unwrapped both fields, we can return our final Success outcome.

fn read_auth_from_headers(headers: &HeaderMap)
  -> Outcome<(String, String), LoginError> {
    let fail = (Status::BadRequest, LoginError::InvalidData);
    option_to_outcome(headers.get_one("username"), fail.clone())
        .and_then(|u| {
            option_to_outcome(
              headers.get_one("password"), fail.clone())
                .and_then(|p| {
                    Outcome::Success(
                      (String::from(u), String::from(p)))
                })
        })
}

Re-Organizing

Armed with this function we can start re-tooling our from_request function. We'll start by gathering the header results and invoking and_then. This unwraps the username and password:

impl<'a, 'r> FromRequest<'a, 'r> for AuthenticatedUser {
    type Error = LoginError;
    fn from_request(request: &'a Request<'r>)
      -> Outcome<AuthenticatedUser, LoginError> {
        let headers_result = 
              read_auth_from_headers(&request.headers());
        headers_result.and_then(|(u, p)| {
            ...
        }
        ...
    }
}

Now for the next step, we'll make a couple database calls. Both of our normal functions return Option values. So for each, we'll create a failure Outcome and invoke option_to_outcome. We'll follow this up with a call to and_then. First we get the user based on the username. Then we find their AuthInfo using the ID.

impl<'a, 'r> FromRequest<'a, 'r> for AuthenticatedUser {
    type Error = LoginError;
    fn from_request(request: &'a Request<'r>)
      -> Outcome<AuthenticatedUser, LoginError> {
        let headers_result = 
              read_auth_from_headers(&request.headers());
        headers_result.and_then(|(u, p)| {
            let conn_str = local_conn_string();
            let maybe_user =
                  fetch_user_by_email(&conn_str, &String::from(u));
            let fail1 =
                 (Status::NotFound, LoginError::UsernameDoesNotExist);
            option_to_outcome(maybe_user, fail1)
                .and_then(|user: UserEntity| {
                    let fail2 = (Status::MovedPermanently, 
                                 LoginError::WrongPassword);
                    option_to_outcome(
                      fetch_auth_info_by_user_id(
                        &conn_str, user.id), fail2)
                })
                .and_then(|auth_info: AuthInfoEntity| {
                   ...
                })
        })
    }
}

This gives us unwrapped authentication info. We can use this to compare the hash of the original password and return our final Outcome!

impl<'a, 'r> FromRequest<'a, 'r> for AuthenticatedUser {
    type Error = LoginError;
    fn from_request(request: &'a Request<'r>)
      -> Outcome<AuthenticatedUser, LoginError> {
        let headers_result = 
              read_auth_from_headers(&request.headers());
        headers_result.and_then(|(u, p)| {
            let conn_str = local_conn_string();
            let maybe_user =
                  fetch_user_by_email(&conn_str, &String::from(u));
            let fail1 =
                 (Status::NotFound, LoginError::UsernameDoesNotExist);
            option_to_outcome(maybe_user, fail1)
                .and_then(|user: UserEntity| {
                    let fail2 = (Status::MovedPermanently, 
                                 LoginError::WrongPassword);
                    option_to_outcome(
                      fetch_auth_info_by_user_id(
                        &conn_str, user.id), fail2)
                })
                .and_then(|auth_info: AuthInfoEntity| {
                   let hash = hash_password(&String::from(p));
                   if hash == auth_info.password_hash {
                        Outcome::Success( AuthenticatedUser{
                            user_id: auth_info.user_id})
                    } else {
                        Outcome::Failure(
                          (Status::Forbidden, 
                           LoginError::WrongPassword))
                    }
                })
        })
    }
}

Conclusion

Is this new solution that much better than our original? Well it avoids the "triangle of death" pattern with our code. But it's not necessarily that much shorter. Perhaps it's a little more cleaner on the whole though. Ultimately these code choices are up to you! Next time, we'll wrap up our current exploration of Rust by seeing how to profile our code in Rust.

This series has covered some more advanced topics in Rust. For a more in-depth introduction, check out our Rust Video Tutorial!

Appendix: Original Function

impl<'a, 'r> FromRequest<'a, 'r> for AuthenticatedUser {
    type Error = LoginError;
    fn from_request(request: &'a Request<'r>) -> Outcome<AuthenticatedUser, LoginError> {
        let username = request.headers().get_one("username");
        let password = request.headers().get_one("password");
        match (username, password) {
            (Some(u), Some(p)) => {
                let conn_str = local_conn_string();
                let maybe_user = fetch_user_by_email(&conn_str, &String::from(u));
                match maybe_user {
                    Some(user) => {
                        let maybe_auth_info = fetch_auth_info_by_user_id(&conn_str, user.id);
                        match maybe_auth_info {
                            Some(auth_info) => {
                                let hash = hash_password(&String::from(p));
                                if hash == auth_info.password_hash {
                                    Outcome::Success(AuthenticatedUser{user_id: 1})
                                } else {
                                    Outcome::Failure((Status::Forbidden, LoginError::WrongPassword))
                                }
                            }
                            None => {
                                Outcome::Failure((Status::MovedPermanently, LoginError::WrongPassword))
                            }
                        }
                    }
                    None => Outcome::Failure((Status::NotFound, LoginError::UsernameDoesNotExist))
                }
            },
            _ => Outcome::Failure((Status::BadRequest, LoginError::InvalidData))
        }
    }
}
Read More
James Bowen James Bowen

Rocket Frontend: Templates and Static Assets

html_braces.png

In the last few articles, we've been exploring the Rocket library for Rust web servers. Last time out, we tried a couple ways to add authentication to our web server. In this last Rocket-specific post, we'll explore some ideas around frontend templating. This will make it easy for you to serve HTML content to your users!

To explore the code for this article, head over to the "rocket_template" file on our Github repo! If you're still new to Rust, you might want to start with some simpler material. Take a look at our Rust Beginners Series as well!

Templating Basics

First, let's understand the basics of HTML templating. When our server serves out a webpage, we return HTML to the user for the browser to render. Consider this simple index page:

<html>
  <head></head>
  <body>
    <p> Welcome to the site!</p>
  </body>
</html>

But of course, each user should see some kind of custom content. For example, in our greeting, we might want to give the user's name. In an HTML template, we'll create a variable or sorts in our HTML, delineated by braces:

<html>
  <head></head>
  <body>
    <p> Welcome to the site {{name}}!</p>
  </body>
</html>

Now before we return the HTML to the user, we want to perform a substitution. Where we find the variable {{name}}, we should replace it with the user's name, which our server should know.

There are many different libraries that do this, often through Javascript. But in Rust, it turns out the Rocket library has a couple easy templating integrations. One option is Tera, which was specifically designed for Rust. Another option is Handlebars, which is more native to Javascript, but also has a Rocket integration. The substitutions in this article are simple, so there's not actually much of a difference for us.

Returning a Template

So how do we configure our server to return this HTML data? To start, we have to attach a "Fairing" to our server, specifically for the Template library. A Fairing is a server-wide piece of middleware. This is how we can allow our endpoints to return templates:

use rocket_contrib::templates::Template;

fn main() {
  rocket::ignite()
    .mount("/", routes![index, get_user])
    .attach(Template::fairing())
    .launch();
}

Now we can make our index endpoint. It has no inputs, and it will return Rocket's Template type.

#[get("/")]
fn index() -> Template {
    ...
}

We have two tasks now. First, we have to construct our context. This can be any "map-like" type with string information. We'll use a HashMap, populating the name value.

#[get("/")]
fn index() -> Template {
    let context: HashMap<&str, &str> = [("name", "Jonathan")]
        .iter().cloned().collect();
    ...
}

Now we have to render our template. Let's suppose we have a "templates" directory at the root of our project. We can put the template we wrote above in the "index.hbs" file. When we call the render function, we just give the name of our template and pass the context!

#[get("/")]
fn index() -> Template {
    let context: HashMap<&str, &str> = [("name", "Jonathan")]
        .iter().cloned().collect();
    Template::render("index", &context)
}

Including Static Assets

Rocket also makes it quite easy to include static assets as part of our routing system. We just have to mount the static route to the desired prefix when launching our server:

fn main() {
    rocket::ignite()
        .mount("/static", StaticFiles::from("static"))
        .mount("/", routes![index, get_user])
        .attach(Template::fairing())
        .launch();
}

Now any request to a /static/... endpoint will return the corresponding file in the "static" directory of our project. Suppose we have this styles.css file:

p {
  color: red;
}

We can then link to this file in our index template:

<html>
  <head>
    <link rel="stylesheet" type="text/css" href="static/styles.css"/>
  </head>
  <body>
    <p> Welcome to the site {{name}}!</p>
  </body>
</html>

Now when we fetch our index, we'll see that the text on the page is red!

Looping in our Database

Now for one last piece of integration with our database. Let's make a page that will show a user their basic information. This starts with a simple template:

<!-- templates/user.hbs -->
<html>
  <head></head>
  <body>
    <p> User name: {{name}}</p>
    <br>
    <p> User email: {{email}}</p>
    <br>
    <p> User name: {{age}}</p>
  </body>
</html>

We'll compose an endpoint that takes the user's ID as an input and fetches the user from the database:

#[get("/users/<uid>")]
fn get_user(uid: i32) -> Template {
    let maybe_user = fetch_user_by_id(&local_conn_string(), uid);
    ...
}

Now we need to build our context from the user information. This will require a match statement on the resulting user. We'll use Unknown for the fields if the user doesn't exist.

#[get("/users/<uid>")]
fn get_user(uid: i32) -> Template {
    let maybe_user = fetch_user_by_id(&local_conn_string(), uid);
    let context: HashMap<&str, String> = {
        match maybe_user {
            Some(u) =>
              [ ("name", u.name.clone())
              , ("email", u.email.clone())
              , ("age", u.age.to_string())
              ].iter().cloned().collect(),
            None =>
              [ ("name", String::from("Unknown"))
              , ("email", String::from("Unknown"))
              , ("age", String::from("Unknown"))
              ].iter().cloned().collect()
        }
    };
    Template::render("user", &context)
}

And to wrap it up, we'll render the "user" template! Now when users get directed to the page for their user ID, they'll see their information!

Conclusion

Next week, we'll go back to some of our authentication code. But we'll do so with the goal of exploring a more universal Rust idea. We'll see how functors and monads still find a home in Rust. We'll explore the functions that allow us to clean up heavy conditional code just as we could in Haskell.

For a more in-depth introduction to Rust basics, be sure to take a look at our Rust Video Tutorial!

Read More