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?
Implementing a Result Applicative With Arrow
In the previous sections, you spent some time understanding how the Arrow code generation process works. You defined your data type, annotated with @higherkind
and generated the code to make it an implementation of the Kind interface. Then you implemented the Arrow interface related to the specific typeclass using the @extension
annotation. After this, you used the generated code in your app.
You can now follow the same process to make the Result<E, T>
data type an applicative.
In the arrow/typeclasses module, create a new file named ResultApplicative.kt and add the following content:
// 1
@extension
// 2
interface ResultApplicative<E> : Applicative<ResultPartialOf<E>> {
// 3
override fun <A> just(a: A): Result<E, A> = Success(a)
// 4
override fun <A, B> Kind<ResultPartialOf<E>, A>.ap(ff: Kind<ResultPartialOf<E>, (A) -> B>): Kind<ResultPartialOf<E>, B> {
// 5
if (ff.fix() is Error<E>) {
return ff.fix() as Error<E>
}
// 6
if (fix() is Error<E>) {
return fix() as Error<E>
}
// 7
val myRes = fix() as Success<A>
val ffRes = ff.fix() as Success<(A) -> B>
val ret = ffRes.a.invoke(myRes.a)
return Success(ret)
}
}
This is the code for the ResultApplicative<E>
where you:
- Use the
@extension
annotation because you’re providing a typeclass implementation for a data type of yours. - Create an interface which extends the
Applicative<ResultPartialOf<E>>
interface. Arrow generated theResultPartialOf<E>
along with theResult<E, T>
data type and it represents the type with just one of the type parameters. - Implement
just()
which is the first of the two functions you need for an Applicative, as you learned in the previous tutorial. - Provide an implementation for
ap
. - Return
ff
if it is anError
. - Do the same if
Result<E, T>
itself is anError
. - Apply the function in
Success
, if successful. You pass this as a parameter to the successful value in theResult<E, T>
itself.
To see how to use this code, you can repeat the same validation example you saw in Functional Programming with Kotlin and Arrow – More on Typeclasses.
Validating With Applicative
To test the generated code for the Applicative
typeclass, create a new file named UserValidation.kt in the main module and add the following code:
// 1
data class User(val id: Int, val name: String, val email: String)
// 2
val userBuilder = { id: Int -> { name: String -> { email: String -> User(id, name, email) } } }
// 3
typealias UserBuilder = (Int) -> (String) -> (String) -> User
In this code, you:
- Define a
User
data class as an example of a model class for a user withid
,name
andemail
properties. - Create a curried version of the function which creates an instance of
User
from the values of its properties. - Define a
typealias
for the previous function type.
You can now add the following code:
fun main() {
// 1
val idAp = 1.just<FetcherException, Int>()
val nameAp = "Max".just<FetcherException, String>()
val emailAp = "max@maxcarli.it".just<FetcherException, String>()
val userAp = userBuilder.just<FetcherException, UserBuilder>()
// 2
val missingNameAp = Error(FetcherException("Missing name!"))
// 3
val errorFunction = { error: FetcherException -> println("Exception $error") }
val successFunction = { user: User -> println("User: $user") }
// 4
Result.applicative<FetcherException>()
.run {
emailAp.ap(nameAp.ap(idAp.ap(userAp)))
}.bimap(errorFunction, successFunction)
// 5
Result.applicative<FetcherException>()
.run {
emailAp.ap(missingNameAp.ap(idAp.ap(userAp)))
}.bimap(errorFunction, successFunction)
}
In this example, you try to create an instance of the User
class if successful or if you encounter an error. Here you:
- Create the
applicatives
forid
,name
andemail
parameters with valid values. You do this usingjust()
. You also defineapplicatives
foruserBuilder
. - Create an
applicative
if you have an invalid value forname
. - Define
successFunction
anderrorFunction
to handle success or errors. - Use the applicative with all valid parameters. It’s interesting to note how an
applicative
is also a Functor and, more specifically, a Bifunctor. - Do the same with invalid parameters, if any.
When you run main()
, you’ll get the following output:
User: User(id=1, name=Max, email=max@maxcarli.it)
Exception com.raywenderlich.fp.FetcherException: Missing name!
If successful, you’ll get a valid User
instance. If not, you’ll get a message regarding the missing name.
Where to Go From Here?
Congratulations! You used Arrow code generation to create a Result<E, T>
data type with the Bifunctor and Applicative typeclasses. In this project, you also have the code for the Monad implementation which you can use as an optional exercise. You’ve come a long way in your study of Functional Programming but the journey’s not over yet! In the next tutorial, you’ll see what Algebraic Data Types are and how you can keep doing magic with them.
You can download a completed version of the project using the Download Materials button at the top or bottom of this tutorial.
If you have any comments or questions, feel free to join in the forum below.