Book Image

Functional Kotlin

Book Image

Functional Kotlin

Overview of this book

Functional programming makes your application faster, improves performance, and increases your productivity. Kotlin supports many of the popular and advanced functional features of functional languages. This book will cover the A-Z of functional programming in Kotlin. This book bridges the language gap for Kotlin developers by showing you how to create and consume functional constructs in Kotlin. We also bridge the domain gap by showing how functional constructs can be applied in business scenarios. We’ll take you through lambdas, pattern matching, immutability, and help you develop a deep understanding of the concepts and practices of functional programming. If you want learn to address problems using Recursion, Koltin has support for it as well. You’ll also learn how to use the funKtionale library to perform currying and lazy programming and more. Finally, you’ll learn functional design patterns and techniques that will make you a better programmer.By the end of the book, you will be more confident in your functional programming skills and will be able to apply them while programming in Kotlin.
Table of Contents (22 chapters)
Title Page
Copyright and Credits
Dedication
Packt Upsell
Contributors
Preface
Index

Other types


Classes, interfaces, and objects are a good starting point for an OOP type system, but Kotlin offers more constructs, such as data classes, annotations, and enums (there is an additional type, named sealed class, that we'll cover later).

Data classes

Creating classes whose primary purpose is to hold data is a common pattern in Kotlin (is a common pattern in other languages too, think of JSON or Protobuff).

Kotlin has a particular kind of class for this purpose:

data class Item(val product: BakeryGood,
  val unitPrice: Double,
  val quantity: Int)

To declare data class, there are some restrictions:

  • The primary constructor should have at least one parameter
  • The primary constructor's parameters must be val or var
  • Data classes can't be abstract, open, sealed, or inner

With these restrictions, data classes give a lot of benefits.

Canonical methods

Canonical methods are the methods declared in Any. Therefore, all instances in Kotlin have them.

For data classes, Kotlin creates correct implementations of all canonical methods.

The methods are as follows:

  • equals(other: Any?): Boolean: This method compares value equivalence, rather than reference.
  • hashCode(): Int: A hash code is a numerical representation of an instance. When hashCode() is invoked several times in the same instance, it should always return the same value. Two instances that return true when they are compared with equals must have the same hashCode().
  • toString(): String: A String representation of an instance. This method will be invoked when an instance is concatenated to a String.

The copy() method

Sometimes, we want to reuse values from an existing instance. The copy() method lets us create new instances of a data class, overriding the parameters that we want:

val myItem = Item(myAlmondCupcake, 0.40, 5)

val mySecondItem = myItem.copy(product = myCaramelCupcake) //named parameter

In this case, mySecondItem copies unitPrice and quantity from myItem, and replaces the product property.

Destructuring methods

By convention, any instance of a class that has a series of methods named component1(), component2() and so on can be used in a destructuring declaration.

Kotlin will generate these methods for any data class:

val (prod: BakeryGood, price: Double, qty: Int) = mySecondItem

The prod value is initialized with the return of component1(), price with the return of component2() , and so on. Although the preceding example use explicit types, those aren't needed:

val (prod, price, qty) = mySecondItem

In some circumstances, not all values are needed. All unused values can be replaced by (_):

val (prod, _, qty) = mySecondItem

Annotations

Annotations are a way to attach meta info to your code (such as documentation, configuration, and others).

Let's look at the following example code:

annotation class Tasty

An annotation itself can be annotated to modify its behavior:

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Tasty

In this case, the Tasty annotation can be set on classes, interfaces, and objects, and it can be queried at runtime.

For a complete list of options, check the Kotlin documentation.

Annotations can have parameters with one limitation, they can't be nullable:

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Tasty(val tasty:Boolean = true)

@Tasty(false)
object ElectricOven : Oven {
  override fun process(product: Bakeable) {
    println(product.bake())
  }
}

@Tasty
class CinnamonRoll : Roll("Cinnamon")

@Tasty
interface Fried {
  fun fry(): String
}

To query annotation values at runtime, we must use the reflection API (kotlin-reflect.jar must be in your classpath):

fun main(args: Array<String>) {
    val annotations: List<Annotation> = ElectricOven::class.annotations

    for (annotation in annotations) {
        when (annotation) {
            is Tasty -> println("Is it tasty? ${annotation.tasty}")
            else -> println(annotation)
        }
    }
}

Enum

Enum in Kotlin is a way to define a set of constant values. Enums are very useful, but not limited, as configuration values:

enum class Flour {
  WHEAT, CORN, CASSAVA
}

Each element is an object that extends the Flour class.

Like any object, they can extend interfaces:

interface Exotic {
  fun isExotic(): Boolean
}

enum class Flour : Exotic {
  WHEAT {
    override fun isExotic(): Boolean {
      return false 
    }
  },

  CORN {
    override fun isExotic(): Boolean {
      return false
    }
  },

  CASSAVA {
    override fun isExotic(): Boolean {
      return true
    }
  }
}

Enum can also have abstract methods:

enum class Flour: Exotic {
  WHEAT {
    override fun isGlutenFree(): Boolean {
      return false
    }

    override fun isExotic(): Boolean {
      return false
    }
  },

  CORN {
    override fun isGlutenFree(): Boolean {
      return true
    }

    override fun isExotic(): Boolean {
      return false
    }
  },

  CASSAVA {
    override fun isGlutenFree(): Boolean {
      return true
    }

    override fun isExotic(): Boolean {
      return true
    }
  };

  abstract fun isGlutenFree(): Boolean
}

Any method definition must be declared after the (;) separating the last element.

When enums are used with when expressions, Kotlin's compiler checks that all cases are covered (individually or with an else):

fun flourDescription(flour: Flour): String {
  return when(flour) { // error
    Flour.CASSAVA -> "A very exotic flavour"
  }
}

In this case, we're only checking for CASSAVA and not the other elements; therefore, it fails:

fun flourDescription(flour: Flour): String {
  return when(flour) {
    Flour.CASSAVA -> "A very exotic flavour"
    else -> "Boring"
  }
}