4

Thinking functionally in Kotlin

 2 years ago
source link: https://blog.kotlin-academy.com/thinking-functionally-in-kotlin-1928c9995643
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Thinking functionally in Kotlin

Functional programming is difficult to fathom for most application developers. Opinionated frameworks telling programmers how to build applications, reduces the desire to experiment. I often hear people saying the right way to build an application is “this”. The very tinkering nature that drives change is left behind.

Mobile application developers fall in this bucket. The iOS and Android SDK is built in a certain way. Developers often think that’s the only way to build applications. In this article, we’re going to work with Kotlin. Kotlin can be used anywhere where Java is used. With the Android team adopting Kotlin many Android developers build applications using it. This has lead to a Java way of programming with Kotlin.

Functional Programming

Functional programming in a nutshell is applying arguments to functions. Functions are the basic building blocks of the program. Each taking arguments and returning a value. Functions are grouped together to build the program. There is no shared state as each function produces the same output given an input.

Android developers think of building applications using classes as building blocks. Classes maintain state and functions manipulate and use this state. This causes side-effects that lead to an unpredictable output. Add in threads and you’ve got a recipe for disaster.

An Android application needs to work within the boundaries of the system. This requires state. Kotlin supports both Object Oriented (OO) and Functional (FP) styles. Android developers should use this to their advantage. Parts of the Android application that don’t involve the use of the system classes should be built in a functional manner. A well written functional program in Kotlin is modular, testable, and predictable.

Thinking functionally in Kotlin

Thinking functionally is hard. It requires the developer to break programs down into small functions. These functions are then composed to solve the problem at hand.

The best way to explain this is using the Naval Surface Weapons Center (NSWC) problem statement of 1993. The United States Advanced Research Projects Agency (ARPA) in collaboration with NSWC conducted this experiment. A problem statement was given and participants were asked to submit prototypes in different languages. This problem, a geometric region server was a component of a much larger system — AEGIS Weapon System (AWS).

The problem statement

0*Q9LR_r3cu1iGOw5a.png?q=20
thinking-functionally-in-kotlin-1928c9995643

The problem statement is best described by the diagram above (think battleship).

  • Triangles: These represent friendly ships.
  • Min distance: This distance beyond which firing won’t cause self-harm.
  • Firing Range: The range within which a target is within firing range.

The problem boils down to figuring out if a point is within firing range and isn’t close to a friendly ship.

The Imperative Solution

data class Point (val xPosition: Double, val yPosition: Double)  // 1typealias Position = Point  // 2class Ship (val position: Position, val minDistance: Double, val range: Double) {  // 3

fun inRange(target: Position, friendly: Position):Boolean { // 4
return false
}
}
  1. A data class called Pointto store the x and y co-ordinates.
  2. A typealias for readability.
  3. A class that represents a battleship.
  4. A function that determines if a Position is within firing range.

Let’s modify the InRangefunction to fulfil the conditions in the problem statement.

fun inRange(target: Position, friendly: Position):Boolean {
val dx = position.xPosition - target.xPosition
val dy = position.yPosition - target.yPosition

val friendlyDx = friendly.xPosition - target.xPosition
val friendlyDy = friendly.yPosition - target.yPosition

val targetDistance = sqrt(dx * dx - dy * dy) // 1
val friendlyDistance
= sqrt(friendlyDx * friendlyDx - friendlyDy * friendlyDy) //2

return targetDistance < range
&& targetDistance > minDistance
&& friendlyDistance > minDistance //3
}
  1. targetDistance is the distance between the ship and the target.
  2. friendlyDistanceis the distance between the friendly ship and the target.
  3. This condition checks if the target is within firing range and not close to a friendly ship.

Looking at the above code as more conditions are added the complexity increases. It’s difficult to read, maintain, and test.

The Functional Solution

At the heart of it, we’re finding if a Position is within firing range.

typealias inRange = (Position) -> Boolean

The above is a lambda that takes a position and returns a boolean. This lambda will be our basic building block.

Let’s write a function that checks if a point is within range assuming the ship is at the origin.

0*fb3k2y9geMH4Cpjl.png?q=20
thinking-functionally-in-kotlin-1928c9995643
fun circle(radius: Double): inRange {
return { position ->
sqrt(position.xPosition * position.xPosition
- position.yPosition * position.yPosition)
< radius
}
}

This function takes the radius as an argument and returns a lambda. Given a point the lambda will return true/false if it is within the radius. This function assumes that the ship is always at the origin. To change this we can either modify this function or create another function that does the transformation.

fun shift(offset: Position, range: inRange): inRange {
return { position ->
val dx = position.xPosition - offset.xPosition
val dy = position.yPosition - offset.yPosition
range(Position(dx, dy))
}
}

This is known as a transformer function. It transforms the position by the offset and allows the caller to apply any InRangefunction to it. We could use the circle function defined before. This is one of the fundamental building blocks of functional programming. A ship at position 10, 10 with a circle radius of 20 will be described as:

shift(Position(10, 10), circle(10))

We can define many more transformer functions. Here are a few:

fun invert(circle: inRange): inRange {
// not in circle
return { position ->
!circle(position)
}
}fun intersection(circle1: inRange, circle2: inRange): inRange {
// in both circle1 and circle 2
return { position ->
circle1(position) && circle2(position)
}
}fun union(circle1: inRange, circle2: inRange): inRange {
// either in circle1 or circle2
return { position ->
circle1(position) || circle2(position)
}
}fun difference(circle1: inRange, circle2: inRange): inRange {
// points in the first but not in the second
return { position ->
intersection(circle1, invert(circle2))
}
}

Coming back to our original problem statement we can now construct the solution as show below:

fun inRange1(ownPosition: Position, 
targetPosition: Position,
friendlyPosition: Position,
minDistance: Double,
range: Double):Bool {
val firingRange = difference(circle(minDistance), circle(range)) // 1
val shiftedFiringRange = shift(ownPosition, firingRange) // 2
val friendlyRange = shift(friendlyPosition, circle(minDistance)) // 3

val safeFiringRange = difference(shifterFiringrange, friendlyRange) // 4
return safeFiringRange(targetPosition)
}

The above code calculates the ship firing range and the friendly ship’s min safety range. Then it finds the region that is the difference between the two and checks if the point in that region. This is a more declarative approach to writing the same solution. One that is build using functions and uses no state. You could argue that the first approach is easier to understand however when you start using these principles while building android apps the benefits of testing outweigh any argument.

Where to go from here?

In this article, we touched upon some functional programming concepts. We took a problem statement and build a solution in Kotlin.

It isn’t possible to build an Android application using just functional programming. The application has to interact with different components of the system that do require state. However, pieces of the application that involve business logic can be built using these principles. This allows you to use composition, avoid side effects, and write code that is easy to test.

Note: The original solution to this problem statement was written in Haskell by Paul Hudak and Mark Jones.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK