Our thoughts, knowledge, insights and opinions

Modes in Rapture and how you can use them to make your code more expressive

Recently I was playing around with Rapture, an awesome utility library created by Jon Pretty. One thing that really stuck with me was the concept of modes. Modes are classes that allow us to modify the type returned by a given library call. You probably wonder how we can do that in Scala. I was curious too, so I dig a bit into the code and this is my answer.

Modes use case - Rapture Json

Here’s a typical use case for Modes in Rapture - parsing Json string. Let’s define few parsing functions

[ModesRunner.scala]

As you can see the only difference between them are the imports. Those imports have a special meaning in Rapture - they are used to bring specific modes into scope. When imported it gets passed as an implicit param into the parse function to modify it’s behavior. Let’s take look on a example

[ModesRunner.scala]

and the output is

The example above doesn’t do much. It shows parsing two Json strings - one correct and one badly formatted, with different modes in scope. As you can see although we are calling the same Json.parse function the results we are getting have different types. Depending on the imported mode we can get either Option, Try or an unwrapped value. In the case of Future mode the result is asynchronous! This gives us a lot of flexibility on how to handle Json. But how does it work? It’s clearly not returning Any - the Future example shows that we can use onSuccess and onFailure without a problem. Is Json.parse overloaded to handle different cases? No. That would be too cumbersome to maintain and extend. So how it’s implemented?

Minimal mode implementation

In order to understand how Rapture achieves this level of modularity, we need to take a step back and try implement something similar, but simpler. When we grasp the idea on a tiny example we will be able to apply our knowledge to a bigger example in Rapture. Our example will cover a simple example: safe division - we want to be able to divide two numbers and get their results. As dividing by 0 is usually an error, we want to catch that.

That’s easy. Here’s a “un-modded” example:

[Math.scala]

Since it will be an utility class we want to allow our end users to decide on how the calculated value will be returned. We will use modes for that. Division using modes is slightly more complex, but not hard to understand. First let’s define a mode. A trait will work fine for that

[Math.scala]

Let’s stop here for a second and discuss this code a bit. First we define ResultWrapper type. As the name suggests it wraps our possible results. It’s generic and parametrized with two types. The first one, being a covariant(+ in the definition) anything(expressed with _) will be used as to hold the correct result. The second type parameter (_ <: Exception will be used to handle errors). Based on that we can define an abstract wrap function. Wrap has two type params Result and ErrorResult. It takes a function, which we will refer to as body, that returns the generic Result. The final return type from wrap is ResultWrapper[Result, ErrorResult]. Nothing new here.

On it’s own a trait won’t do much. Let’s implement concrete classes - modes for Option and Try.

[Math.scala]

The Try example is slightly easier, so let’s discuss it first. We define ResultWrapper as simple Try. We keep it generic, so we can use tryMode with every body we want to. The wrap function is also very straightforward - we simple invoke the body inside a Try to catch the output - both successful divisions and exceptions. The second example does exactly the same thing, but it’s slightly longer due to the explicit try-catch. Nothing fancy.

Now that we have all the pieces in place we can move to solve the problem at hand. Here’s the upgraded divide method.

[Math.scala]

This time it takes a third param, being the mode. We could pass it as an implicit as Rapture does it, but implicit isn’t really required here. The code block with division is passed to mode.wrap for evaluation and from that we get the final result. You might be surprised but this is all it takes to write a moddable function from scratch.

One thing to notice in the example above is the (explicit for clarity) return type - mode.ResultWrapper[Int, ArithmeticException]. It’s parametrized with ArithmeticException to handle the possible divide by zero error and with Int which is the expected type for Int by Int division. If you compare it with our modes you will quickly understand how the return type is modified … in fact it stays the same! The only difference that for tryMode mode.ResultWrapper[Int, ArithmeticException] is defined as Try[Int] and for optionMode mode.ResultWrapper[Int, ArithmeticException] is a Option[Int]. So we keep it typesafe.

(Question for the curious: In this example we are using Int as the type for division although Double would be more useful, do you know why? If so, leave us a comment below.)

Writing our custom Rapture mode

Now let’s move back to Rapture. For a better understanding of modes we will write ourselves one. It won’t do anything sensible, just return a one or zero element Seq[T].

[CustomMode.scala]

As you can see it’s not very different from our previous code. We made it implicit for easier passing and we implement the interface Mode[ParseMethods] required by Rapture. Turns out it’s all it takes.

Modes outside Rapture

As we established earlier you can use modes in Rapture and with custom code, but what about all the code you’ve written before and you would like to “mode-ify”? When working with Rapture you can do that in no-time. Just use the convenient modally methods.

[ModesRunner.scala]

In this case we transform the return type from T to Option[T], but you could easily pass the mode as parameter.

Summary

As you can see implementing modes is surprisingly easy and comes a with few benefits. The biggest one being the fact, that they don’t force the end users of our API to tie closely to a single return type. With the moddable approach the decision on how to serve the data is made by end users, which know exactly what is the best fit for their needs. Since they aren’t tied they are also free to experiment and change their code without having to wonder if the API will work fine with their improved codebase.

I hope I managed to convince you that modes are a very useful tool for writing flexible code. Especially when you don’t know how your API will be used by client code. Again Scala’s type system proved to be powerful enough to express even the most difficult type requirements. Modes are not the only cool thing that you can find in Rapture. I encourage you to check it out.

PS. Huge thanks to Rapture committers for creating this library, especially Jon Pretty for creating Rapture and giving a talk that inspired me to write this post.

You like this post? Want to stay updated? Follow us on Twitter or subscribe to our Feed.