Functional Programming with Kotlin and Arrow – Generate Typeclasses With Arrow
In this Kotlin tutorial, you’ll take the functional programming concepts learned in previous tutorials and apply them with the use of the Arrow framework. By Massimo Carli.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Functional Programming with Kotlin and Arrow – Generate Typeclasses With Arrow
25 mins
- Getting Started
- Implementing the Result<E, A> Datatype With Arrow
- Seeing How Arrow Can Help?
- Generating Arrow Code
- Looking at the Generated Code
- Using the Generated Code
- Implementing a Bifunctor With Arrow
- Examining the Generated Code for Bifunctor
- Using an Alternative Option
- Implementing a Result Applicative With Arrow
- Validating With Applicative
- Where to Go From Here?
In the previous tutorial Functional Programming with Kotlin and Arrow Part 3: More about Typeclasses, you created a simple app using typeclasses to fetch information from a remote endpoint. You used the Result<E,A>
datatype along with the functions for making it a Bifunctor, an Applicative and, finally, a Monad. In future tutorials, you’ll have the opportunity to further your understanding of the theory behind these important FP concepts. For this tutorial, you’ll implement the features of Result<E,A>
that we created in previous tutorial with the Arrow framework.
Working through this tutorial, you’ll:
- Learn how to define a data type with Arrow which enables code generation with the use of the
@higherkind
annotation. - Understand the Arrow
Kind
abstractions and how the generated code uses it. - Create a
bifunctor
typeclass implementation forResult<E,A>
using the@extension
annotation. - Use the same process for the implementation of the applicative version of
Result<E,A>
. - Use the applicative in a classic validation use case.
Time to do some coding magic! :]
Getting Started
Download the materials for this tutorial using the Download Materials button at the top or bottom of this page. Open the project using IntelliJ 2019.x or greater. Here’s the structure of the project:
It’s important to note that:
- You’re going to write most of
main()
in the external src folder. This is where FunctionalFetcher.kt is. - The arrow module contains the data-types and typeclasses submodules. These depend on the Arrow library. You’ll need two modules because the code generation must be done before the one for typeclasses — which usually depends on the previous one.
Start by opening FunctionalFetcher.kt and locating the following code:
// 1
object FunctionalFetcher {
fun fetch(url: URL): String {
try {
// 2
with(url.openConnection() as HttpURLConnection) {
requestMethod = "GET"
// 3
val reader = inputStream.bufferedReader()
return reader.lines().asSequence().fold(StringBuilder()) { builder, line ->
builder.append(line)
}.toString()
}
} catch (ioe: IOException) {
// 4
throw FetcherException(ioe.localizedMessage)
}
}
}
The code above:
- Defines
FunctionalFetcher
which usesfetch()
to get content from the network given a URL parameter. - Opens
HttpURLConnection
with HTTP’sGET
. - Reads and accumulates all the lines into a
String
usingStringBuilder
. - Throws
FetcherException
, if any errors occur. The exception is declared in the FetcherException.kt file in the arrow/data-types module.
You can test the previous code running the following main
method found in the same FunctionalFetcher.kt file.
fun main() {
// 1
val ok_url = URL("https://jsonplaceholder.typicode.com/todos")
// 2
val error_url = URL("https://error_url.txt")
// 3
println(FunctionalFetcher.fetch(ok_url))
}
This code is very simple. It:
- Defines the
ok_url
variable you can use to testFunctionalFetcher
with a successful result. - Uses
error_url
to test for errors. - Invokes
fetch()
with one of the previous parameters.
If you run main()
using ok_url
, you’ll get output like this:
[ { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false },
- - -
{ "userId": 10, "id": 200, "title": "ipsam aperiam voluptates qui", "completed": false }]
If you use error_url
, you’ll get this output instead.
Exception in thread "main" com.raywenderlich.fp.FetcherException: error_url.txt
In the previous tutorial, Functional Programming with Kotlin and Arrow: More on Typeclasses, you learned how to use the Result<E, A> data type that you implemented from scratch. You can now do the same with the Arrow framework and see how this framework can make the process simpler.
Implementing the Result<E, A> Datatype With Arrow
First, create a new file named Result.kt in the arrow/data-type module and add the following code:
// 1
sealed class Result<out E, out A>
// 2
class Success<out A>(val a: A) : Result<Nothing, A>()
// 3
class Error<out E>(val e: E) : Result<E, Nothing>()
You’ll notice that it’s the same code you wrote in the previous tutorial. It:
- Defines
Result<E,T>
using a sealed class. - Creates
Success<T>
, if successful. This encapsulates a result of typeT
. - Creates
Error<E>
, if it fails. This encapsulates the exception of typeE
.
Seeing How Arrow Can Help?
But how can Arrow help? Arrow helps in the implementation of the typeclasses. But in order to do this, you need to make your Result<E,T>
an implementation of the Kind
interface. There are different … kinds (:]) of the Kind
interface depending on the number of type parameters. For this Result<E,T>
, you just need the following:
@documented
interface Kind<out F, out A>
typealias Kind2<F, A, B> = Kind<Kind<F, A>, B>
If you write your data type as an implementation of the Kind
interface, Arrow can generate all the code you need. To do so, replace the previous code with the following:
// 1
@higherkind
// 2
sealed class Result<out E, out A> : ResultOf<E, A> {
// 3
companion object
}
class Success<out A>(val a: A) : Result<Nothing, A>()
class Error<out E>(val e: E) : Result<E, Nothing>()
There are some important things to note:
- You use the
@higherkind
annotation to enable the Arrow code generation for data types. - The
Result<E,T>
sealed class now implements theResultOf<E, A>
interface which is not available yet. You’ll learn about this very soon. - In order to simplify the code generation, Arrow needs an empty companion object which is used as an anchor point.
Generating Arrow Code
The previous code doesn’t compile because Arrow didn’t generate the code yet. You can do so by building the arrow/data-types module from your terminal using the following command:
./gradlew :arrow:data-types:build
You can also run the same task using the equivalent option in the Gradle tab in IntelliJ:
Now the warnings in IntelliJ should disappear and the code should compile successfully.
But what is the ResultOf<E, A>
interface?
Looking at the Generated Code
Building the arrow/data-types enables the Arrow code generation. With @higherkind
annotation, you generate the code found in the arrow/data-types/build/generated/source/kaptKotlin folder:
In your case, the generated code is the following:
// 1
class ForResult private constructor() { companion object }
// 2
typealias ResultOf<E, A> = arrow.Kind2<ForResult, E, A>
// 3
typealias ResultPartialOf<E> = arrow.Kind<ForResult, E>
// 4
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
inline fun <E, A> ResultOf<E, A>.fix(): Result<E, A> =
this as Result<E, A>
You’ve already seen similar code in the Functional Programming with Kotlin and Arrow Part 2: Categories and Functors tutorial. But in this case, you have:
- The definition of the
ForResult
type which might remind you of theNothing
type because it cannot have instances. - The
ForResult
type as the first type parameter forKind2
. -
ResultPartialOf<E>
as a convenience typealias you’ll use later for the implementation of the Bifunctor, Applicative and Monad typeclasses. - The
fix()
method which will be very useful when you need to cast theResultOf<E, A>
type to theResult<E, A>
you created earlier.
But what can you do now with the new ResultOf<E, A>
implementation?