Explaining the Arrow Android sample
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 result5
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 Concurrent
through 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:
function
getAllNewsOur block previously declared with fx.concurrent
is 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 insideIO
will not throw exceptions until it is run, and those exceptions can be captured insideIO
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 flatMap
etc. 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
- ArrowFx documentation
- Functional Programming in Scala
- Kotlin Functional Programming, Does it make sense?
- Kotlin coroutines with arrow-fx
Acknowledgements
Jorge Castillo is the author of the ArrowKt Android sample
About the author
David Rawson is an Android developer at Trade Me.