Explaining the Arrow Android sample

David Rawson
Apr 1, 2020 · 5 min read

Arrow of outrageous fortune

Arrow is an exciting development for Kotlin developers interested in functional programming and, more broadly, pushing the limits of the Kotlin compiler. It was recently lauded in Thoughtworks Tech Radar:

Our initial positive impressions of Arrow were confirmed when using it to build applications that are now in production.

That being said, it’s not an easy framework to learn. There’s a sample Android project by Jorge Castillo, but it can be a bit intimidating. Let’s try and unpack some of the functional programming (FP) goodness in this article.

Package structure

In FP, we aim for an application constructed with pure functions. Pure functions are functions with no side effects.

No side effects is a lofty goal when we consider pushing Fragments onto the backstack is a side effect, as is saving to shared preferences, as is updating content in a RecyclerView. We can identify these side effects by looking for functions with return type Unit, where “Unit” means “no meaningful return since it only performs side effects”.

Since these functions are so common, how would we write an Android app without them?

In practicality, we have to follow the direction of the FP bible:

We make more functions pure, and push side effects to the outer layers. We could call these impure functions the imperative shell around the pure “core” of the program.

This explains the first design choice of the ArrowKt sample app. There are two root packages: algebra and runtime.

The pure core is called an “algebra” because operating therein we will maintain referential transparency:

  • Because the important functions therein are pure, they will always return the same result given the same input.
  • Because of this, when reasoning about the program we can replace these functions with the results of evaluating them, much like we can replace the expression 2 + 3 with the result 5 without changing the meaning when we are performing algebra.

In this vein, the red book says:

When expressions are referentially transparent, we can imagine that computation proceeds much like we’d solve an algebraic equation. We fully expand every part of an expression, replacing all variables with their referents, and then reduce it to its simplest form.

More precisely, the functions in the algebra package make

… a tree of declarative and deferred computations to implement your system logic…done with algebraic data types that define the operations your program is able to perform.

Runtime

The other package, runtime, contains the “impure shell” mentioned in the red book: the Activity and Application classes that are entry points that will runthe pure functions. Apart from these entry points, we have the following file IORuntime.kt:

We declare an abstract class called Runtime with a single type parameter F. The class has a field, context, containing the dependencies we will need to run our app: two coroutine dispatchers and an API service.

Concurrent

It also implements Concurrent<F> by delegating to the constructor parameter concurrent. In other words, we can supply an instance of Concurrent<F> in the constructor and then the instance of Runtime will implement all the functions of Concurrentthrough forwading calls to the constructor parameter.

What is Concurrent? According to the docs, it is a type class for async data types that are cancelable and can be started concurrently. In practice, this means we can use the fx property of Concurrent to write a series of effectful operations in an embedded domain-specific language:

While this may look strange, the ! is actually an operator overload. Under the hood, it calls a method called bind(), as per the following equivalent snippet:

Both syntaxes !effect {} and effect {}.bind() means “apply this side effect”. The computation that represents applying all of these effects is wrapped in the fx.concurrent block. Notice that the getNews().attempt() call is asynchronous. Like Kotlin coroutines, with this syntax you can write async code as if it were blocking code without having to use map, flatMap etc.

Executing at the edge of the world

The computation can only be executed at the impure edge of the world, for example in this Activity:

Our block previously declared with fx.concurrentis be executed with one of two available strategies: blocking and non-blocking. Since both execution strategies have side effects, they must run inside an unsafe block.

Polymorphism

Notice how Runtime has a single type parameter F. With our invocation of the extension method IO.runtime inside the block we bind F to arrow.fx.IO:

IO is just a data type to represent side-effects. According to the docs,

IO is used to represent operations that can be executed lazily, and are capable of failing, generally with exceptions. This means that code wrapped inside IO will not throw exceptions until it is run, and those exceptions can be captured inside IO for the user to check.

But because of the type parameter, we can bind to another class if we wanted to. For example, if we wanted to change the engine to use RxJava2 and use an existing scheduler we could do the following:

Even though we have changed the underlying engine, we don’t need to change any of the algebra code. All we do is add the extra dependnecy io.arrow-kt:arrow-fx-rx2 or one of the other adapters for common async frameworks.

Remember our getAllNewsMethod written with ConcurrentSyntax inside the fx.concurrent block?

If we put a breakpoint right on the highlighted line, we can see what is happening from swapping Runtime from arrow.fx.IO to RxJava2:

Arrow Fx has taken the instructions written in ConcurrentSyntax DSL and translated it into a series of calls with Rx using flatMapetc. Magic!

Notice how this kind of change would be impossible without the type parameter F. If you’ve ever migrated an app from AsyncTask to RxJava, or from RxJava to RxJava2, or from RxJava2 to Coroutines you’ll appreciate this.

Further reading

Acknowledgements

Jorge Castillo is the author of the ArrowKt Android sample

About the author

David Rawson is an Android developer at Trade Me.

Default to Open

Life lives here. Stories about how we're building Trade Me.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store