Functional Programming with Kotlin and Arrow – More on Typeclasses

Continuing the Functional Programming with Kotlin and Arrow Part 2: Categories and Functors tutorial, you’ll now go even further, using a specific and common use case, with a better understanding of data types and typeclasses, from Functor to Monad, passing through Applicatives and Semigroups. By Massimo Carli.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Presenting Monads

In the previous section, you implemented Result<E,A> as a functor and applicative that you can use for the FunctionalFetcher. The result is a JSON you store as a type String. In practice, you need to parse the JSON and return an object of a different type.

Create a file named FunctionalFetcherApp.kt into the main module. Then copy 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))
    }
  }
}

This should be nothing new. You’ve simply:

  1. Defined fetch() using the Result<FetcherException,String> as a return type.
  2. Returned a Success<String> if you successfully receive some data.
  3. Returned an Error<FetcherException> in case of an error.

In case of success, you’ll get a text containing the JSON. It will look like this:

[ 
   { 
      "userId":1,
      "id":1,
      "title":"delectus aut autem",
      "completed":false
   },
   - - -
]

It contains the JSON for an array of items that you can represent with the following data class, which you can copy in the same Kotlin file. It also represents a Task in an application for your TODOs.

@Serializable
data class Task(val userId: Int, val id: Int, val title: String, val completed: Boolean)
Note: This example uses the koltinx serialization plugin. But that plugin is beyond the scope for the current tutorial.

You now need to parse JSON into a List<Task>. You can do this with the following function copied into the same file:

fun parseTasks(jsonData: String): Result<JsonDecodingException, List<Task>> {
  val json = Json(JsonConfiguration.Stable)
  try {
    return Success(json.parse(Task.serializer().list, jsonData))
  } catch (ex: JsonDecodingException) {
    return Error(ex)
  }
}

This code tries to parse String in input. It returns a Success<List<Task>> in case of success or an Error<JsonDecodingException> in case of error.

Suppose you want to use this function to parse the result of your FunctionalFetcherResult object. You can then copy the following code into the same FunctionalFetcherApp.kt:

fun main() {
  val ok_url = URL("https://jsonplaceholder.typicode.com/todos")
  val error_url = URL("https://error_url.txt")
  // 1
  FunctionalFetcherResult.fetch(ok_url).mapRight {
    // 2
    parseTasks(it)
  }.mapLeft {
    // 3
    println("Error $it")
  }.mapRight {
    // 4
    println("Output $it")
  }
}

In this code you’ve:

  1. Invoked fetch() with the ok_url.
  2. Used parseTasks() as a parameter to map().
  3. Printed the error message in case of an error.
  4. Printed the result in case of success.

Build and run. In case of success, you’ll get an output like the following:

Output com.raywenderlich.fp.Success@131276c2

This is because of the parseTasks() in mapRight. It returns a Result<JsonDecodingException, List<Task>>, which is the one you print in the end. In case of success, it would be nice to have List<Task> directly as an implicit parameter for the last mapRight. This is where the monad comes in.

Creating the Result<E, T> Monad

Crete a new ResultMonad.kt in the typeclass sub-module. Copy the following code:

// 1
fun <E, T, R> Result<E, T>.flatMap(fn: (T) -> Result<E, R>): Result<E, R> = when (this) {
  // 2
  is Success<T> -> fn(this.a)
  // 3
  is Error<E> -> this
}

Here you define a monad typeclass with flatMap(). This function:

  1. Has a parameter of type (T) -> Result<E, R>.
  2. Returns the result of the function you get as parameter on the current value. In this case, the current object is a Success<T>.
  3. Returns the same object in case of an Error<E>.

Now you can replace main() in FunctionalFetcherApp.kt with the following:

fun main() {
  val ok_url = URL("https://jsonplaceholder.typicode.com/todos")
  val error_url = URL("https://error_url.txt")
  FunctionalFetcherResult.fetch(ok_url)
    .flatMap(::parseTasks) // HERE
    .mapLeft {
      println("Error $it")
    }.mapRight {
      println("Output $it")
    }
}

You just replaced the first mapRight() invocation with the flatMap() you just define. Build and run.

If successful, you’ll now get an output like this:

Output [Task(userId=1, id=1, title=delectus aut autem, completed=false), Task(userId=1, id=2, title=quis ut nam facilis et officia qui, completed=false), 
- - -
Task(userId=10, id=200, title=ipsam aperiam voluptates qui, completed=false)]

Now, if successful, the last mapRight() receives the result of parseTasks(), a function that can fail. In that case, you’d still get the JsonDecodingException as the type of the implicit parameter for mapLeft().

Where to Go From Here?

Congratulations! You just wrote a bunch of code. You implemented a Result<E, T> data type, and you handled the superpower of a bifunctor, an applicative and a monad. You also had the chance to use the semigroup typeclass in case of error management.

Your journey through the world of functional programming doesn’t end here, though. In the next tutorial, you’ll learn how Arrow can help generate most of the code you just wrote from scratch. You’ll have the chance to work on this same project and see how Arrow can help with the implementation of the FunctionalFetcher use case.

You can download a complete 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.