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?
Using the Generated Code
Now you have everything you need for a better implementation of FunctionalFetcher.
The FuntionalFetcherResult file

The FuntionalFetcherResult file
Create a file named FunctionalFetcherResult.kt in the same package of the FunctionalFetcher.kt file in the main module. Add the following code:
object FunctionalFetcherResult {
// 1
fun fetch(url: URL): Result<FetcherException, String> {
try {
with(url.openConnection() as HttpURLConnection) {
requestMethod = "GET"
val reader = inputStream.bufferedReader()
val result = reader.lines().asSequence().fold(StringBuilder()) { builder, line ->
builder.append(line)
}.toString()
// 2
return Success(result)
}
} catch (ioe: IOException) {
// 3
return Error(FetcherException(ioe.localizedMessage))
}
}
}
In this code, you:
- Replace the previous return type
StringwithResult<FetcherException, String>. - Return the result encapsulated in a
Success<String>, if successful. - Return the exception into an object of type
Error<FetcherException>, if you encounter an error.
If you want to test the previous code, you need something different from the main() method in the previous implementation. You need a different behavior if the result is a Success than what you’d want if the result is an Error. Specifically, you need a Bifunctor.
Implementing a Bifunctor With Arrow
In the Functional Programming with Kotlin and Arrow – More on Typeclasses tutorial you learned what a Bifunctor is. It is a way to apply different functions to Kind2 depending on its actual type. For the Result<E,T> data type, it’s a way to apply a different function for Success<T> versus an Error<E>.
Implementing a Bifunctor with Arrow is relatively simple; you just need to create an implementation of the existing arrow.typeclasses.Bifunctor interface which requires the following operation:
fun <A, B, C, D> Kind2<F, A, B>.bimap(fl: (A) -> C, fr: (B) -> D): Kind2<F, C, D>
The fl is the function you’ll apply for an Error and fr for Success.
To implement this, create a new file named ResultBifunctor.kt in the arrow/typeclasses module and add the following code:
// 1
@extension
// 2
interface ResultBifunctor : Bifunctor<ForResult> {
// 3
override fun <A, B, C, D> Kind2<ForResult, A, B>.bimap(fl: (A) -> C, fr: (B) -> D): Kind2<ForResult, C, D> {
val fixed = fix()
return when (fixed) {
// 4
is Error<A> -> Error(fl(fixed.e))
// 5
is Success<B> -> Success(fr(fixed.a))
}
}
companion object
}
In this code, you define an interface that:
- Uses the
@extensionto enable Arrow code generation for typeclasses. - Extends the
Bifunctorinterface for theForResulttype which is the one you use in theKind2abstraction. - Provides implementation for the
bimap()function. - Uses the
fixedversion of theResultobject checking if it’s anErroror aSuccess. If the former, it appliesfland returns the result into anotherErrorobject. - Applies
fr, if you have a success and returns the result into a newSuccessobject.
You can now run this command from the terminal:
./gradlew :arrow:typeclasses:build
…or use the same option in the Gradle tab. Either method will trigger the Arrow code generation for typeclasses.
Build Typeclasses

Build Typeclasses
Examining the Generated Code for Bifunctor
After the execution of the previous command, you’ll see a new file in the build/generated/source/kaptKotlin/main folder of the typeclasses module:
Generated code for the Bifunctor typeclass

Generated code for the Bifunctor typeclass
If you have a look at the content of the ResultBifunctor.kt file, you’ll see the code that Arrow generated. You can find the bimap() function implementation with the following signature:
fun <A, B, C, D> Kind<Kind<ForResult, A>, B>.bimap(arg1: Function1<A, C>, arg2: Function1<B, D>):
Result<C, D>
You can also find utility functions like:
fun <A, B, C> Kind<Kind<ForResult, A>, B>.mapLeft(arg1: Function1<A, C>): Result<C, B>
…which allow you to apply a single function to the E part of Result<E, T>.
Note the functions with the following signatures:
fun <X> rightFunctor(): Functor<Kind<ForResult, X>>
fun <X> leftFunctor(): Functor<Conested<ForResult, X>>
These allow you to use the E and T parts as different Functors.
For a better understanding of this, go back to the FunctionalFetcherResult.kt file in the main module and add the following code after the FunctionalFetcherResult implementation:
fun main() {
// 1
val ok_url = URL("https://jsonplaceholder.typicode.com/todos")
val error_url = URL("https://error_url.txt")
// 2
val errorFunction = { error: FetcherException -> println("Exception $error") }
// 3
val successFunction = { json: String -> println("Json $json") }
FunctionalFetcherResult
.fetch(ok_url)
.bimap(errorFunction,successFunction) // 4
}
In this code, you:
- Define the
ok_urlanderror_urlvariable in order to test your code. - Create
errorFunction()to use for Error. This prints the encapsulated exception. - Create
successFunction, for success. This prints the result. - Invoke
bimap()with the previous functions as parameters.
Note that bimap() is the one Arrow generates for you for the Kind2 implementation of ForResult
You can now run the code and see the different behavior for an error versus success.
Using an Alternative Option
You can also use the generated code in a different, more complex way. Replace main() in the previous block of code with the following:
fun main() {
// 1
val ok_url = URL("https://jsonplaceholder.typicode.com/todos")
val error_url = URL("https://error_url.txt")
// 2
val result = FunctionalFetcherResult.fetch(error_url)
// 3
when (result) {
is Success<String> -> manageSuccess(result)
is Error<FetcherException> -> manageError(result)
}
}
Here you simply:
- Define
ok_urlanderror_urlto test your code. - Invoke
fetch()to returnResult<E, T>. - Invoke
manageSuccess()ormanageError()depending on the resulting type.
To compile, you’ll need to add the following as well:
fun manageSuccess(result: Success<String>) {
// 1
val successFunction = { json: String -> println("Json $json") }
val rightFunctor = Result
.bifunctor() // 2
.rightFunctor<String>() // 3
.lift(successFunction) // 4
rightFunctor(result) // 5
}
fun manageError(result: Error<FetcherException>) {
// 6
val errorFunction = { error: FetcherException -> println("Exception $error") }
val leftFunctor = Result
.bifunctor()
.leftFunctor<FetcherException>()
.lift(errorFunction)
// 7
leftFunctor(result.conest())
}
This code defines the manageSuccess() and manageError() functions. Here you:
- Define the
successFunctionfunction which simply prints the content of the response. - Invoke
bifunctor()onResult, after code generation makes it available. This will get the reference to theBifunctorimplementation. - Invoke
rightFunctor<String>()to get the reference to the Functor for the right part ofResult, which isString.
Note: A Functor is a high order function which maps a function from a type(A) -> Bto a function of typeF(A) -> F(B). In your case, F is the right part of theResult<E, T>. - Pass the reference to
successFunction()as a parameter oflift(). If successful, you’ll get another function you can apply to the result. - Apply
rightFunctor()to the result. - Do the same in
manageError()but for the left part of theResult<E, T>. - Apply
leftFunctorto the conest version of the result. Note: You can get more information regarding this on the Arrow website. In general, a Conest> is a way to represent a function from a typeAto a typeKind<F,A,C>.
Run the new main() implementation and verify that the behavior is the same for success or an error. You’ll find that the previous approach is better. But this last approach demonstrates a possible use of the Arrow generated code for Bifunctor.