/ FUNCTIONAL PROGRAMMING, ARROW, IO

From Imperative to Functional Programming using Arrow

Some time ago, I watched the talk FP to the max. While the end of the talk is quite Scala-ish, the beginning can be of interest regardless of the language.

In the talk, the speaker tries to migrate a standard imperative-like application using a functional approach. I wanted to check if it was possible to do the same in Kotlin.

This is the 1st post in the From Imperative to Functional Programming focus series.Other posts include:

  1. From Imperative to Functional Programming using Arrow (this post)
  2. From Imperative to Functional Programming, an approach
  3. From Imperative to Functional Programming: a grouping issue (and how to solve it)
  4. From Imperative to Functional Programming: the Dijkstra algorithm

The original code

Translated in Kotlin, the original code looks like the following:

private val random = SecureRandom()

fun main(args: Array<String>) {
  println("What is your name?")
  val name = readLine()
  println("Hello, $name, welcome to the game!")
  var exec = true
  while(exec) {
    val number = random.nextInt(5) + 1
    println("Dear $name, please guess a number from 1 to 5:")
    val guess = readLine()?.toInt()
    if (guess == number) println("You guessed right, $name!")
    else println("You guessed wrong, $name! The number was $number")
    println("Do you want to continue, $name?")
    when(readLine()) {
      "y" -> exec = true
      "n" -> exec = false
    }
  }
}

Partial functions

Functions are at the foundation of Function Programming.

Among them, partial functions are an issue. Those are functions that cannot be executed for some specific values. For example, / is a partial function, because no number can be divided by 0. In the above snippet, there are 2 partial functions.

In Java, calling a partial functions with an invalid value would cause an exception. This exception could be caught and managed at any place in the calling stack. This is unfortunately not possible with FP.

Let’s check how we can handle that in a FP-friendly way.

Calling toInt() on a String

Invalid values are those that are not numbers.

The FP approach is to wrap the call in a Try object. It’s trivial to create and use:

fun parseInt(input: String?) = Try { input?.toInt() }

val guess: Try<Int?> = parseInt(readLine())
when(guess) {
  is Failure -> println("You did not enter a number!")
  is Success<Int?> -> {
    if (guess.value == number) println("You guessed right, $name!")
    else println("You guessed wrong, $name! The number was $number")
  }
}

Actually, this is still imperative programming. Try offers true FP capabilities with the fold() method. It requires two lambda parameters, the first one executed in case of failure, the second one in case of success:

(readLine() as String)                                                 (1)
  .safeToInt()                                                         (2)
  .fold(
    { println("You did not enter a number!") },                        (3)
    {                                                                  (4)
      if (it == number) println("You guessed right, $name!")
      else println("You guessed wrong, $name! The number was $number")
    }
  )

private fun String.safeToInt() = Try { this.toInt() }
1 Needs to be cast to the non-nullable String, as it cannot be null in that case
2 The `parseInt() can better be handled as an extension function
3 Will be called if the String cannot be parsed to a number
4 Will be called if it can
Deciding whether to continue, or not

The snippet handles only y and n, but not other values. We just need a third branch, to handle values that are invalid.

Let’s add it:

var cont = true
while(cont) {
  cont = false
  println("Do you want to continue, $name?")
  when (readLine()?.toLowerCase()) {
    "y"  -> exec = true
    "n"  -> exec = false
    else -> cont = true                       (1)
  }
}
1 Irrelevant inputs are managed

Introducing IO

The IO pattern is a widespread pattern in FP. It involves wrapping values, exceptions and lambdas in IO instances, and passing them around.

Arrow is FP library for Kotlin. It provides FP abstractions, such as IO.

The following diagram is an (incomplete) overview of the IO class in Arrow:

io class diagram

With IO, every system call can be wrapped in pure functions with IO instances:

private fun putStrLn(line: String): IO<Unit> = IO { println(line) }
private fun getStrLn(): IO<String?> = IO { readLine() }

From loops to recursivity

FP eschews state: a pure function’s return value should only depend on its input parameter(s) - and not state.

Loops, whether for or while, use state to break out of the loop. Hence, loops are not FP-friendly. In FP, loops should be replaced with recursive calls.

For example, the continue loop can be replaced with:

private fun checkContinue(name: String?): Boolean {
  println("Do you want to continue, $name?")
  (readLine() as String).transform { it.toLowerCase() }.transform {
    when (it) {
      "y" -> true
      "n" -> false
      else -> checkContinue(name)
    }
  }
}

private fun <T> String.transform(f: (String) -> T) = f(this) (1)
1 The default String.map() method iterates over the characters of the String

Let’s wrap the above snippet in IO:

private fun checkContinue(name: String?): IO<Boolean> = IO.monad().binding { (1)
  putStrLn("Do you want to continue, $name?").bind()
  (getStrLn()).map { it?.toLowerCase() }.map {
    when (it) {
      "y" -> true.liftIO()                                                   (2)
      "n" -> false.liftIO()
      else -> checkContinue(name)                                            (3)
    }
  }
  .flatten()                                                                 (4)
  .bind()                                                                    (5)
}
.fix()                                                                       (6)
1 The IO.monad().binding method provides a context for IO
2 Transforms a simple scalar value into an IO
3 Cannot bind() here as the binding context is not available
4 Flattens the IO<IO<?>> into a simple IO<?>
5 Binds the IO to its wrapped value
6 Downcasts the type, from IOOf<A> to IO<A>

Triggering the wrapper

IO are wrappers around computations, but no computation actually occurs until a termination method is called. They are several such methods available:

  • unsafeRunSync()
  • unsafeRunAsync()
  • runAsync()
  • etc.

For our example, the unsafeRunSync() is more than enough in our case:

fun main(args: Array<String>) {
  println("What is your name?")
  val name = readLine()
  println("Hello, $name, welcome to the game!")
  gameLoop(name).unsafeRunSync()                (1)
}
1 Triggers the computation

This can be pushed "to the max" like that:

fun main(args: Array<String>) {
  mainIO(args).unsafeRunSync()
}

private fun mainIO(args: Array<String>): IO<Unit> = IO.monad().binding {
  putStrLn("What is your name?").bind()
  val name = getStrLn().bind()
  putStrLn("Hello, $name, welcome to the game!").bind()
  gameLoop(name).bind()
}.fix()

Some more nitpicking

IO offers a map() method. It can be used to transform the value returned after reading the value input by the user, e.g.:

private fun checkContinue(name: String?): IO<Boolean> = IO.monad().binding {
  putStrLn("Do you want to continue, $name?").bind()
  (getStrLn()).map { it.toLowerCase() }.map {                                     (1)
    when (it) {
      "y" -> true.liftIO()
      "n" -> false.liftIO()
      else -> checkContinue(name)
    }
  }.flatten()
   .bind()
}.fix()

private fun gameLoop(name: String?): IO<Unit> = IO.monad().binding {
  putStrLn("Dear $name, please guess a number from 1 to 5:").bind()
  getStrLn().safeToInt().fold(
      { putStrLn("You did not enter a number!").bind() },
      {
        val number = nextInt(5).map { it + 1 }.bind()
        if (it.bind() == number) println("You guessed right, $name!")
        else putStrLn("You guessed wrong, $name! The number was $number").bind()
      }
  )
  checkContinue(name).map {
    (if (it) gameLoop(name)
    else Unit.liftIO())
  }.flatten()
   .bind()
}.fix()

private fun nextInt(upper: Int): IO<Int> = IO { random.nextInt(upper) }
private fun IO<String>.safeToInt() = Try { map { it.toInt() }}
1 No need for transform() anymore

Conclusion

Kotlin, with the help of Arrow, makes it possible to move from legacy imperative code to a more functional-oriented one. Whether it’s relevant or not depends one on’s context. In all cases, that’s one more trick in one’s sleeve.

The complete source code for this post can be found on Github.
Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Also double as a trainer and triples as a book author.

Read More
From Imperative to Functional Programming using Arrow
Share this