Kotlin is arising as one of the most powerful functional languages, and developers around the world are using it for personal and professional projects. In this talk, Jorge will showcase a Kotlin approach to learn how to implement a functional oriented architecture for our Android apps. He will also showcase a functional programming library he is working on in the Spanish community, called KΛTEGORY.
My name is Jorge Castillo, and I work for GoMore, an international company based in Copenhagen, Denmark. GoMore is all about ridesharing, car rental, and car leasing.
What is Functional Programming?
Separation of concerns is important to functional programming.
Purity is another part of functional programming. Suppose I call a function 1000 times with a parameter, I should get the same result every time. The code should be deterministic and predictable.
Functional programming also has the advantage of referential transparency and readability. I should be able to know a function’s return and input types.
Functional programming avoids state.
Functional Features of Kotlin
Kotlin has features found in other functional programming languages, such as Haskell: high-order functions, functions as first-class citizens, operator overloading, and type inference.
Kotlin is not a functional language by design, but it is capable of being used as one.
The motivation behind KΛTEGORY is to create a library for the language that would provide data types and abstractions to do functional programming in Kotlin.
Let’s try to use this new library to solve some key problems that we can find in any possible system when we play with languages that operate on the Java Virtual Machine: 1) modeling error and success cases, 2) asynchronous code and threading, 3) side effects, 4) dependency injection, and 5) testing.
For example, you should be able to model success and error results when your system queries an external source, like a database or an API. We deal with side effects all the time in object-oriented languages because that’s very related to state. In object-oriented languages, each object has its own memory state, so objects are very prone to having side effects.
Get more development news like this
Modeling Error and Success Cases
If we think about the vanilla process that we have been doing for a long time in Java, it could be something like this:
Here is some code to “fetch a hero”. We have a use case wrapping that bridge or the main logic inside of our domain layer. There’s a method that asks for a page of heroes, and a callback to provide results because it’s an asynchronous call. We are injecting a data source and a logger, which could be an implementation using Fabric.
Then we notify the previous thread with the valid list of heroes. If anything goes wrong, you want to catch those exceptions produced by the external source systems, and then notify the previous thread using the callback. Perhaps you also want to log errors using the Fabric logger implementation, just in case you want to have some clues about errors happening in the domain layer in the future.
But, this is not ideal. First, we need to wrap exceptions in the domain layer, and then map them to a different notification system, and notify the previous thread using a callback.
Here, there are two ways of propagating the error, and we need to switch or swap between them, just because exceptions do not surpass thread limits. If I just don’t catch an exception here, and there is some presenter or view model that executes or calls this code and is running it on a different thread, using a
ThreadPoolExecutor for example, the error would get totally lost. It would never reach the presenter because exceptions are not capable of surpassing thread limits so the presenter could be waiting forever for a response
We are breaking referential transparency. The method’s return type does not reflect what the method returns. We need to use a callback, and the callback does not say anything about the error type, meaning we need to get inside of the callback, look at its implementation to notice that there is also a possibility that this function could return an error. This is not ideal.
Alternative 1: A Wrapper Class
Let’s try wrapping the result inside of some wrapper class like this:
In this constructor, you can have an error or success with helper methods react to different ways. We may want to declare our expected errors in the domain layer as an enum because we are still not using Kotlin, here. We can just refactor the data source to return results which are very explicit about the possibility of returning an error or a list of heroes.
The issue with this is that it would not work with asynchronous threading.
Alternative 2: RxJava
Another alternative uses RxJava. For example, we have the data source API implementation using a Marvel API to get the heroes. We construct an observable using a
Single, an observable that is going to be dispatched just once. The moment an observer subscribes to it, it fetches the heroes. A notification is made if everything turns out right, and there can also be a notification if there’s an error.
onError termination events are not the best way to notify about errors in RxJava. You have other systems and operators to recover from errors. Assume that the
dataSource implementation returns an observable to which we can subscribe.
The use case here is simpler now. You can map over the observable with business logic. For example, you could discard some non-valid heroes that don’t have valid portrait images, then react to errors here. Everything is wrapped in a single stream, so it’s much better because you have a single stream of data where both concerns about error and success results are included. It’s better than the previous example, but we still don’t have the error type in the return type.
However, you could use a different type, like the
Maybe type. You can take care of threading using schedulers, which is very well integrated with the RxJava API.
Alternative 3: KΛTEGORY
Let’s try to solve the same problem using KΛTEGORY. KΛTEGORY provides a new class called
Either is like
Maybe. Its value could be a left or a right - it contains two different values because it’s a sealed class.
We will use Kotlin now for our example. We have our domain exceptions or errors modeled as a sealed class because we have a sealed hierarchy of those errors, and a sealed class is a good way to model them.
We can do a new, different implementation of the
dataSource. We pass in the dependencies as function parameters, fetch characters using the service, and the result will be our collection of heroes or network DTOs.
The use case looks very simple now because we can apply a fold function over the
Either that is returned by the
dataSource. Folding over that means that I need to pass two different lambdas which are applied depending on whether it’s an error or a success value.
The presenter of the new model that calls this code can be simple. It can also fold over the returned
Either, and apply different view rendering effects or orders for the view contract, depending on success or failure. You can see the methods we are using to render the errors or the valid list of heroes on the screen -
For errors, there’s a matching over the error type that we have inside of a sealed class. We are getting the benefit from Kotlin’s when statement that gives us exhaustive evaluation with sealed classes and implicit castings. A different effect will be applied depending on the error.
Asynchronous Code and Threading
There are a few different alternatives to handle threading and synchronicity. Recall for the Java project, we used
ThreadPoolExecutor and exceptions and callbacks for mapping errors. RxJava was more about using schedulers, with observables and error subscriptions.
This is handled differently in KΛTEGORY. Here is a computation that is applied to an external data source - for example, an API client calling an API or maybe the implementation of the data source could be put in a database - so it’s like an external computation. It’s an I/O computation, and we need to wrap that inside of an I/O monad.
We keeping building our return type here. We have the
Either which could be an error or a valid list of heroes. This is now wrapped inside of an
IO is wrapping a computation which is a threading type and which could be returning either an error or a valid list of heroes. Different things are modeled inside of the same type: the
IO computation plus the duality about what it could be returning.
The use case is easy again, but this time what we get back from the data source is an
IO and not an
Either anymore. As a result, I need to map this twice. The first mapping returns the
Either. Then I map the
Either again to discard the non-valid heroes.
The presentation logic is doing something interesting because we get the
IO back wrapping an
Either. This is the moment for me to have the
IO resolve and applies its effects. I get access, I tell it explicitly to run and unfold and actually execute its inner computation, and then the result would be folded again to apply different effects depending on the case.
However, this is not ideal. I do not want to apply side effects inside my presenter or my theme model. Ideally, I want to push side effects to the edge of the system where the frameworks are bounded. In Android, this means the view implementation. I want to let the View decide when side effects are going to be applied, so the rest of my architecture is completely pure in avoiding side effects.
To solve this problem, we can use lazy evaluation. Any step inside our architecture is modeled to return a function instead of the already computed value. That means it’s returning something expecting to get run, but it has not been run yet. This is heavily related to functional programming.
Passing in dependencies all the way down across different layers is not ideal, and we should avoid that.
We can use something called the Reader monad as a solution. What it does is wrap a computation and defer it in time. The Reader has a context and its dependencies are implicitly passed.
It has some weaknesses. Even if it’s solving a problem of deferring function execution or computation execution and also implicitly passing dependencies, it’s not capable of playing with what functional programming calls computations returning monadic values which is just
IO and so on.
As before, we wrap it inside of a Reader. Now, we have a deferred
IO computation that could be either returning an error or a list of heroes. Here, the return type does exactly what we want it to do. It’s a Reader wrapped in a computation, and because we do not need to pass the dependencies explicitly anymore, we get automatic access to them. This can be accomplished by calling the
ask function. It automatically wraps a computation with this type.
The presenter could look like this. We again
ask to lift up a Reader from the context value so we
flatMap over it and get access to the context. Here inside of the parentheses is the context itself, but I am applying a data class deconstruction, which is just a feature of the language, to get rapid access to its properties.
When testing, I just need to pass a different context which mocks some of those dependencies. So the context could easily be thought of like dagger graphs, for example. It’s going to be the dependency bindings or the dependency tree with all the already-resolved dependencies.
We need to get rid of all those nested types that we had before. Monads do not compose very gracefully when you have different types of monads on your stack. Using Monad transformers is a way to gift already existing monads with new powers. If I have an
IO, I can gift the
IO with the powers of an
Either also, and make it work as if it was an
Either or an
We want to have an
IO with the powers of
Either and Readers, so that in the end we have a super-type which can resolve all the concerns we had on our stack. We can call this
This wraps all our concerns in a single type, and not three different nested levels. We are providing for the first time over Kotlin using KΛTEGORY which had monad bindings. This is like the do-notation in Haskell.
I can now run more asynchronous calls and as many as I need. This can also be done sequentially.
The presentation code does not need to chance, and it can look as simple as this.
Advanced Functional Programming Styles
Tagless final is about declaring your whole execution chain, and not using concrete classes like
IO, Reader, or
Either, but using type classes. Type classes define behavior. If we define our whole execution tree based on behaviors, then we can pass the implementation details later to decide about that. Our entire execution tree is defined depending on abstractions, and not concrete things. There’s a sample in the repo and also a pull request describing the problem in a very good way.
Free monads are a similar concept.
Free is a way to wrap a computation and declare it in a completely abstract way. We compose an abstract syntax tree are nested computations; all are applied using
Free. So, on each one of those levels, we return
Free contained in a computation. As a result, we are not declaring semantics or implementation details until the very last moment.
Free can easily replace dependency injection because we are deferring the implementation details for a later moment in time. So dependency injection is not needed anymore. If you want to test anything composed of
Free instances you just need to use a different interpreter, passing different implementation details, like mocks for all the dependencies or whatever you want. There’s a sample repo implementing the same android app but using the
Free monads style.
The patterns we learned today can be easily applied to any possible platform or system because we are solving completely generic problems like dependency injection.
You could have the same exact patterns on your backend or frontend side or wherever you are using Kotlin. Even if you are not using Kotlin, and you are using Scala or Haskell, you can still apply the same concepts, because they are wrapping solutions for completely generic problems.
Functional programming is really about fixing things once and not doing it again.
All of the sample code can be viewed here in this sample repo. You have the same application implemented four times, and each one takes a different approach.
About the content
This talk was delivered live in October 2017 at Mobilization. The video was transcribed by Realm and is published here with the permission of the conference organizers and speakers.