These Changes


Sep 12, 2019

Today let us visit a lesser known data type: These. I came across it first while working at Formation. I can't remember the first use of it, but I do remember how I ended up using it a while later, and a colleague using it in the exact same way; vindication, yes! So what we'll cover in this post is what the These data type is, how it can be used, and how we used it to manage feature flags & safe code migrations.

This, That, and the Other

These can be thought of as Either, but extend it with an extra case where one variant can hold both values. Code speaks louder than words so here's what it would look like in Haskell:

Earlier we said that These can be thought of as an extended Either, so let's examine the similarities. With Either we have the Left case which holds a value of type a and Right which holds a value of type b.

With These we have a This case which holds a value of type a (similar to Left) and a That case which holds a value of type b (similar to Right). We extend our two cases with one more case, These, which holds two values, one of type a and one of type b. Another way to think about this is that we're removing the mutual exclusivity that we see using Either by allowing both values to occur at once as well.

Those Useful Functions

What kind of things can we do with These? What's in our toolbox with this new data type? We have our usual suspects, These is a Functor so we can use fmap on it:

We also have Applicative and Monad instances as long as the a is a Semigroup:

What we can glean from this behaviour is that it is short-circuiting when encountering This unless we also encounter a These, then we want to gather as while also preserving the b value.

The final useful instance (in my humble opinion) in the These toolbox is its Bifunctor instance:

This allows to apply functions to transform any of the values inside. We can target the as, the bs, or both.

Migrating Them

Now that we know how to use These, let's talk about one use case for the data type. When I was working on a couple of features in Formation I was adding functionality to an existing code path. A healthy dose of paranoia meant that we wanted make these changes so that we could deploy things in a phased fashion. We would create an enumeration that would look like the following:

Let's take an example of what one of these migrations would involve. In one case we were validating data. Initially this was a simple look up of a Map and placing the values into a domain type. Roughly speaking, we would have a function validate like:

Later we wanted to supercharge this validation by inspecting the values and checking invariants on them. For example, if we were dealing with numbers we would check that they were greater than zero. The aforementioned paranoia set in and we wanted to ensure that any data that was already persisted could still be validated. We already had persisted data that got past the previous validation function but we cannot be sure that it would pass the new validation.

Imagine the following scenario. We add a check in our validation saying that we do not want to process lists that are longer than 5. Unbeknownst to us we have persisted a list that is of length 6. This data is in production and is technically still valid. In the future this would prevented by our validation logic, but for now it lives on. So essentially we want to have backwards compatibility while we monitor things that go wrong.

So where does These come into this? Well we conveniently have a three-way case of calling code! We want to make sure that any errors that can happen in any of the code paths are handled and placed in the This case. If we are successful in validating without any errors we return our errors in That. The interesting case is where we want to fall back on the old validation when the new validation fails. If the new code path fails but the old code path succeeds we will return the result in These. Let's look at some pseudo-code to drive this point home.

From there we can call validate and if we want to keep track of any deviations in the SafeChange case, we can log messages and/or metrics:

Conclusion

So next time you're considering adding some new functionality you want to carefully monitor, consider using the these package and the technique above. For posterity's we will also link the monad-chronicle package which is the Monad Transformer for These. And for some self-promotion, if you're also in the Rust world I ported some of this functionality in my these crate.