Published on [Permalink]
Reading time: 20 minutes
Posted in:

Kotlin Functional Programming

Kotlin Functional Programming

Note this blog post contains my notes from the Functional Programming section of the book Atomic Kotlin. Consider buying a copy of the book for a more detailed reference.

Lambdas

Lambdas produce compact code that is easier to understand.

Lambdas are AKA function literal.

Lambdas require a minimal amount of code to create and can be inserted directly into other code.

The map() function works with collections such as List. It applies a tranasformation function to each element in the collection returning a new List.

Example:

import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  val result = list.map({n: Int -> "[$n]" })
  result eq listOf("[1]", "[2]", "[3]", "[4]")
}

The parameter list is separated by the function body by an arrow

The function body can be one or more expressions. The final expression becomes the return value of the lambda.

Simpler syntax where type inference is avialable:

import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  val result = list.map({ n -> ["$n]"})
  result eq listOf("[1]", "[2]", "[3]", "[4]")
}

If there’s only a single parameter, Kotlin generates the name it for that parameter resulting in yet simpler syntax

import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  val result = list.map({ "[$it]" })
  result eq listOf("[1]", "[2]", "[3]", "[4]")
}

If the lambda is a trailing arg to a function or the only arg to a function then you can remove the parentheses.

import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  val result = list.map { "[$it]" }
  result eq listOf("[1]", "[2]", "[3]", "[4]")
}

If the function takes more than one arg, all except the last lambda arg must be in parentheses.

Example with joinToString():

import atomictest.eq

fun main() {
  val list = listOf(9, 11, 23, 32)
  list.joinToString(" ") { "[$it]" } eq
    "[9] [11] [23] [32]"
}

If you want to provide the lambda as a named arg you must place the lambda in parentheses

import atomictest.eq

fun main() {
  val list = listOf(9, 11, 23, 32)
  list.joinToString(
    separator = " ",
    transform = { "[$it]" }
  ) eq "[9] [11] [23] [32]
}

Syntax for a lambda with more than one parameter:

import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c')
  list.mapIndexed { index, element ->
    "[$index: $element]"
  } eq listOf("[0: a]", "[1: b]", "[2: c]")
}

You can ignore particular args with an _

import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c')
  list.mapIndexed { index, _ ->
    "[$index]"
  } eq listOf("[0]", "[1]", "[2]")
}

Alternative way of writing the above:

import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c')
  list.indices.map {
    "[$it]"
  } eq listOf("[0]", "[1]", "[2]")
}

Lambdas can have zero args as well. Then the arrow is optional but recommended:

import atomictest.eq

fun main() {
  run { -> trace("A lambda") }
  run { trace("Without args") }
  trace eq """
    A lambda
    Without args
  """
}

run simply calls its lambda arg

Example of a lambda to select only even elements:

Without Lambda:

package importanceoflambdas
import atomictest.eq

fun filterEven(nums: List<Int>): List<Int> {
  val result = mutableList<Int>()
  for (i in nums) {
    if (i % 2 == 0)
      result.add(i)
  }
  return result
}

fun main() {
  filterEven(listOf(1, 2, 3, 4)) eq
    listOf(2, 4)
}

With Lambda:

import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  val even = list.filter { it % 2 == 0 }
  even eq listOf(2, 4)
}

Functional programming solves problems in small steps.

You can store a lambda in var or val:

import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  val transform = { n: Int -> "[$n]" }
  val result = list.map(transform)
  result eq
    listOf("[1]", "[2]", "[3]", "[4]")
}

Lambdas can refer to elements outside their scope. When a function closes over elements in its environment we call it a closure.

Lambda’s and closures are distinct features.

Example of a lambda that closes over environment:

import atomictest.eq

fun main() {
  val list = listOf(1, 5, 7, 10)
  val divider = 5
  list.filter { it % divider == 0 } eq
    listOf(5, 10)
}

In the above example the lambda captures divider

Lambdas can modify captured values like sum below:

import atomictest.eq

fun main() {
  val list = listOf(1, 5, 7, 10)
  val sum = 0
  val divider = 5
  list.filter { it % divider == 0 }
      .forEach { sum += it }
  sum eq 15
}

Alternative code without changing the state of sum in the environment:

import atomictest.eq

fun main() {
  val list = listOf(1, 5, 7, 10)
  val divider = 5
  list.filter { it % divider == 0 }
      .sum() eq 15
}

An ordinary function can also close over its env

Operations on Collections

We can create lists in many ways:

import atomictest.eq

fun main() {
  val list = List(10) { it }
  list eq "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
  
  val list2 = List(10) { 0 }
  list2 eq "[0, 0, 0, 0, 0, ,0, 0, 0, 0, 0]"
  
  val list3 = List(10) {'a' + it }
  list3 eq "[a, b, c, d, e, f, g, h, i, j]"
  
  val list4 = List(10) {list3[it % 3] }
  list4 eq "[a, b, c, a, b, c, a, b, c, a]"
}

List() and MutableList() are not constructors just functions named with uppercase letters to make them look like constructors

some other collection functions:

Here destructuring is used with a custom function:

package operationsoncollections
import atomictest.eq

fun createPair() = Pair(1, "one")

fun main() {
  val (i, s) = createPair()
  i eq 1
  s eq "one"
}

Using sumOf and sortedBy

package operationsoncollections
import atomictest.eq

data class Product(
  val description: String,
  val price: Double
)

fun main() {
  val products = listOf(
    Product("bread", 2.0),
    Product("wine", 5.0)
  )
  
  products.sumOf { it.price } eq 7.0
  products.sortedByDescending { it.price } eq
    "[Product(description=wine, price=5.0)," +
      " Product(description=bread, price=2.0)]"
}

Example using takeLastWhile and dropWhile:

import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c', 'X', 'Y')
  list.takeLast(3) eq listOf('c', 'X', 'Y')
  
  list.takeLastWhile { it.isUpperCase() } eq
    "[X, Y]"
  
  list.drop(1) eq "[b, c, X, Y]"
  list.dropWhile { it.isLowerCase() } eq
    "[X, Y]"
}

When applying filter and map to a set the functions return a List

Member References

Member references for functions, properties, and constructors can replace trivial lambdas that simply call the corresponding function, property, or constructor.

Example member reference

package memberreferences1
import atomictest.eq

data class Message(
  val sender: String,
  val text: String,
  val isRead: Boolean
)

fun main() {
  val messages = listOf(
    Message("Kitty", "Hey", true)
    Message("Kitty", "Where are you?", false))
    val unread = messages.filterNot(Message::isRead)
    unread.size eq 1
    unread.single().text eq "Where are you?"
}

Property references are useful when specifying a non-trivial sort order:

import memberreferences.Message
import atomictest.eq

fun main() {
  val messages = listOf(
    Message("Kitty", "Hey!", true),
    Message("Kitty", "Where are you?", false),
    Message("Boss", "Meeting today", false)
  )
  
  messages.sortedWith(compareBy(
    Message::isRead, Message::sender)) eq
    listOf(Message("Boss", "Meeting today", false),
      Message("Kitty", "Where are you?", false),
      Message("Kitty", "Hey!", true))
}

sortedWith() sorts a list using a comparator. Comparator’s are objects that compare two elements. Its args are predicates.

compareBy() with 1 arg is equivalent to sortedBy()

In Kotlin you can’t pass a function where a function type is expected, but you can pass a reference to that function:

package memberreferences2
import atomictest.eq

data class Message(
  val sender: String,
  val text: String,
  val isRead: Boolean,
  val attachments: List<Attachment>
)

data class Attachment(
  val type: String,
  val name: String
)

fun Message.isImportant(): Boolean =
  text.contains("Salary increase") ||
    attachments.any {
      it.type =="image" &&
        it.name.contains("cat")
    }

fun main() {
  val messages = listOf(
    Message("Boss", "Let's discuss goals " +
    "for next year", false,
    listOf(Attachment("image", "cute cats"))
    )
  )
  messages.any(Message::isImportant) eq true
}

References are not limited to member functions like in the above example.

If you have a top-level function taking Message as its only parameter, you can pass it as a reference. When you create a reference to a top-level function , there’s no class name:

package memberreferences2
import atomictest.eq

fun ignore(message: Message) =
  !message.isImportant() &&
    message.sender in setOf("Boss", "Mom")

fun main() {
  val text = "Let's discuss goals for next year"
  val msgs = listOf(
    Message("Boss", text, false, listOf()),
    Message("Boss", text, false listOf(
      Attachment("image", "cute cats"))
    )
  )
  msgs.filter(::ignore).size eq 1
  msgs.filterNot(::ignore).size eq 1
}

You can create a reference to a constructor using the class name:

package memberreferences3
import atomictest.eq

data class Student(
  val id: Int,
  val name: String
)

fun main() {
  val names = listOf("Alice", "Bob")
  val students = names.mapIndexed { index, name ->
    Student(index, name)
  }
  students eq listOf(Students(0, "Alice"),
    Student(1, "Bob"))
    names.mapIndexed(::Student) eq students
}

Higher-Order Functions

A language supports higher-orer functions if its functions can accept other funcs as args and produce functions as return values.

Storing a lambda in a reference

package higheroderfunctions
import atomictest.eq

val isPlus: (Int) -> Boolean = { it > 0 }

fun main() {
  listOf(1, 2, -3).any(isPlus) eq true
}

Type annotations for functions follows:

(Arg1, Arg2, ...) -> Return Type

Calling a function through a reference:

package higherorderfunctions
import atomictest.eq

val helloWorld: () -> String =
  { "Hello, world!" }

val sum: (Int, Int) -> Int =
  { x, y -> x + y }

fun main() {
  helloWorld() eq "Hello, world!"
  sum(1, 2) eq 3
}

When a function accepts a function parameter you can pass either a function reference or a lambda.

package higherorderfunctions
import atomictest.eq

fun <T> List<T>.any(
  predicate: (T) -> Boolean
): Boolean {
  for (element in this) {
    if predicate(element) {
      return true
    }
    return false
  }
}

fun main() {
  val ints = listOf(1, 2, -3)
  ints.any { it > 0 } eq true
  
  val strings = listOf("abc", " ")
  strings.any { it.isBlank() } eq true
  strings.any(String::isNotBlank) eq true
}

Another example:

import atomictest.eq

fun main() {
  repeat(4) { trace("hi") }
  trace eq "hi hi hi hi"
}

How repeat may be implemented:

package higherorderfunctions
import atomictest.eq

fun repeat(
  times: Int,
  action: (Int) -> Unit
) {
  for (index in 0 until times) {
    action(index)
  }
}

fun main() {
  repeat(3) { trace("$it") }
  trace eq "0 1 2"
}

A function return type can be nullable:

import atomictest.eq

fun main() {
  val transform: (String) -> Int? =
    { s: String -> s.toIntOrNull() }
    
  transform("112") eq 112
  tranasform("abc") eq null
}

The whole function type can also be nullable:

fun main() {
  val mightBeNull: ((String) -> Int)? = null
  if (mightBeNull != null) {
    mightBeNull("abc"
  }
}

Manipulating Lists

zip() combines two lists by mimicking the behavior of the zipper on your jacket pairing adjacent List elements:

import atomictest.eq

fun main() {
  val left = listOf("a", "b", "c", "d")
  val right = listOf("q", "r", "s", "t")
  
  left.zip(right) eq
    "[(a, q), (b, r), (c, s), (d, t)]"
  left.zip(0..4) eq
    "[(a, 0), (b, 1), (c, 2), (d, 3)]"
  (10..100).zip(right) eq
    "[(10, q), (11, r), (12, s), (13, t)]"
}

Zipping left and right results in a List of Pairs.

You can also zip a list with a range like in the above example.

Zip can also perform operations on each Pair it creates:

package manipulatinglists
import atomictest.eq

data class Person(
  val name: String,
  val id: Int
)

fun main() {
  val names = listOf("Bob", "Jill", "Jim")
  val ids = listOf(0, 1, 2)
  val names.zip(ids) { name, id ->
    Person(name, id)
  } eq "[Person(name=Bob, id=0), " +
    "Person(name=Jill, id-1), " +
    "Person(name=Jim, id=2)]"
}

To zip two adjacent elements from a single List use zipWithNext():

import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c', 'd')
  list.zipWithNext() eq listOf(
    Pair('a', 'b'),
    Pair('b', 'c'),
    Pair('c','d'))
  
  list.zipWithNext { a, b -> "$a$b" } eq
    "[ab, bc, cd]"
}

flatten() takes a List of Lists and flattens it into a List of single elements

import atomictest.eq

fun main() {
  val list = listOf(
    listOf(1, 2),
    listOf(3, 4),
    listOf(5, 6)
  )
  list.flatten() eq "[1, 2, 3, 4, 5, 6]"
}

Let’s produce all possible Pairs of a range of Ints

import atomictest.eq

fun main() {
  val intRange = 1..3
  intRange.map { a ->
    intRange.map { b -> a to b }
  } eq "[" +
    "[(1, 1), (1, 2), (1, 3)], " +
    "[(2, 1), (2, 2), (2, 3)], " +
    "[(3, 1), (3, 2), (3, 3)]" +
    "]"
    
  intRange.map { a ->
    intRange.map { b -> a to b }
  }.flatten() eq "[" +
  "(1, 1), (1, 2), (1, 3), " +
  "(2, 1), (2, 2), (2, 3), " +
  "(3, 1), (3, 2), (3, 3)" +
  "]"
  
  intRange.flatMap { a ->
    intRange.map { b -> a to b }
  } eq "[" +
  "(1, 1), (1, 2), (1, 3), " +
  "(2, 1), (2, 2), (2, 3), " +
  "(3, 1), (3, 2), (3, 3)" +
  "]"
}

Another example

package manipulatinglists
import kotlin.random.Random
import atomictest.*

enum class Suit {
  Spade, Club, Heart, Diamond
}

enum class Rank(val faceValue: Int) {
  Ace(1), Two(2), Three(3), Four(4), Five(5),
  Six(6), Seven(7), Eight(8), Nine(9),
  Ten(10), Jack(10), Queen(10), King(10)
}

class Card(val rank: Rank, val suit: Suit) {
  override fun toString() {
    return "$rank of ${suit}s"
  }
}

val deck: List<Card> =
  Suit.values().flatMap { suit ->
    Rank.values().map { rank ->
      Card(rank, suit)
    }
  }

fun main() {
  val rand = Random(26)
  repeat(7) {
    trace("'${deck.random(rand)}'")
  }
  trace eq """
    'Jack of Hearts' 'Four of Hearts'
    'Five of Clubs' 'Seven of Clubs'
    'Jack of Diamonds' 'Ten of Spades'
    'Seven of Spades'
  """
}

Building Maps

Creating a repeatable set of data:

package buildingmaps

data class Person(
  val name: String,
  val age: Int
)

val names = listOf("Alice", "Arthricia", "Bob", 
  "Bill", "Birdperson", "Charlie",
  "Crocubot", "Franz", "Revolio")
val ages = listOf(1, 2, 3,
  3, 4, 5,
  4, 2, 6)

fun people(): List<Person> =
  names.zip(ages) { name, age -> 
    Person(name, age)
  }

fun main() {
  val map: Map<Int, List<Person>> =
    people().groupBy(Person::age)
  map[2] eq listOf(Person("Arthricia", 2))
  map[4] eq listOf(Person("Birdperson", 4),
    Person("Crocubot", 4)
}

groupBy in the above example creates a map of ages to Person objects.

Example using associateWith():

import buildingmaps.*
import atomictest.eq

fun main() {
  val map: Map<Person, String> =
    people().associateWith {it.name }
  
  map[Person("Alice", 1)] eq "Alice"
}

associateBy is used in the same way but the lambda specifies the key not value

associateBy() must be used with a unique selection Key and returns a Map that paires each unique key to a single element selected by that key.

Example using getOrElse():

import atomictest.eq

fun main() {
  val map = mapOf(1 to "one", 2 to "two")
  map.getOrElse(0) { "zero" } eq "zero"
  val mutableMap = map.toMutableMap()
  mutableMap.getOrPut(0) { "zero" } eq "zero"
  mutableMap eq "{1=one, 2=two, 0=zero}"
}

We can transform a Map using map(), mapKeys(), or mapValues():

import atomictest.eq

fun main() {
  val even = mapOf(2 to "two", 4 to "four")
  
  even.map {
    "${it.key}=${it.value}"
  } eq listOf("2=two", "4=four")
  
  even.map { (key, value) ->
    "$key=$value"
  } eq listOf("2=two", "4=four")
  
  even.mapKeys { (num, _) -> -num }
    .mapValues { (_, str) -> "minus $str" } eq
    mapOf(-2 to "minus two", -4 to "minus four")
    
  even.map { (key, value) ->
    -key to "minus $value"
  }.toMap() eq
    mapOf(-2 to "minus two", -4 to "minus four")
}

In the above example:

You can also use any() or all() with Maps.

You can also use maxByOrNull() which finds the maximum entry given the criteria

Sequences

A kotlin Sequence is like a List, but you can only iterate through a Sequence. You cannot index into a Sequence.

Sequences aka streams in other languages.

Operations on Lists are performed eagerly (they happen right away). The first operation must finish before the next operation begins.

For long Lists this may be suboptimal.

In lazy evaluation a result is only computed when needed.

If the final result of a calculation is found before processing the last element no further elements are processed.

Use asSequence() to convert List to Sequence. All List operations except indexing are available for Sequences.

package sequences
import atomictest.*

fun Int.isEven(): Boolean {
  trace("$this.isEven()")
  return this % 2 == 0
}

fun Int.square(): Int {
  trace("$this.square()")
  return this * this
}

fun Int.lessThanTen(): Boolean {
  trace("$this.lessThanTen()")
  return this < 10
}

fun main() {
  val list = listOf(1, 2, 3, 4)
  trace(">>> List:")
  trace(
    list
      .filter(Int::isEven)
      .map(Int::square)
      .any(Int::lessThanTen)
  )
  trace(">>> Sequence:")
  trace(
    list.asSequence()
      .filter(Int::isEven)
      .map(Int::square)
      .any(Int::lessThanTen)
  )
  
  trace eq """
    >>> List:
    1.isEven()
    2.isEven()
    3.isEven()
    4.isEven()
    2.square()
    4.square()
    4.lessThanTen()
    true
    >>> Sequence:
    1.isEven()
    2.isEven()
    2.square()
    4.lessThanTen()
    true
}

Calling filter() or map() on a Sequence produces another Sequence.

Nothing happens until you ask for a result from a calculation:

import atomictest.eq
import sequences.*

fun main() {
  val r = listOf(1, 2, 3, 4)
    .asSequence()
    .filter(Int::isEven)
    .map(Int::square)
  r.toString().substringBefore("@") eq
    "kotlin.sequences.TransformingSequence"
}

We would need to use a Terminal Sequence operation to get a result. This includes any() or toList().

A terminal operation runs all stored operations in the process:

import sequences.*
import atomictest.*

fun main() {
  val list = listOf(1, 2, 3, 4)
  trace(
    list.asSequence()
      .filter(Int::isEven)
      .map(Int::square)
      .toList()
  )
  
  trace eq """
    1.isEven()
    2.isEven()
    2.Square()
    3.isEven()
    4.isEven()
    4.square()
    [4, 16]
  """
}

Sequences can call operations in any order resulting in lazy evaluation.

generateSequence() creates a sequence of infinite numbers using an initial number and a lambda:

import atomictest.eq

fun main() {
  val naturalNumbers =
    generateSequence(1) { it + 1 }
  naturalNumbers.take(3).toList() eq
    listOf(1, 2, 3)
  naturalNumbers.take(10).sum() eq 55
}

Another example using generateSequence() without the first parameter:

import atomictest.*

fun main() {
  val items = mutableListOf(
    "first", "second", "third",
    "XXX", "4th"
  )
  val seq = generateSequence {
    items.removeAt(0).takeIf { it != "XXX" }
  }
  seq.toList() eq ["first, second, third]"
  capture {
    seq.toList()
  } eq "IllegalState Exception: This " +
    "sequence can only be consumed once"
}

To make multiple passes through a sequence convert it to some type of Collection

Example implementation of takeIf() that can work with any type

package sequences
import atomictest.eq

fun <T> T.takeIf(
		predicate: (T) -> Boolean
): T? {
		return if (predicate(this)) this else null
}

fun main() {
		"abc".takeIf { it != "XXX" } eq "abc"
		"XXX".takeIf { it != "XXX" } eq null
}

Local Functions

You can define functions within other functions

Named functions defined within other functions are called local functions.

Eample:

import atomictest.eq

fun main() {
  val logMsg = StringBuilder()
  fun log(message: String) =
    logMsg.appendLine(message)
  log("Starting computation")
  val x = 42
  log("Computation result: $x")
  logMsg.toString() eq """
    Starting Computation
    Computation result: 42
  """
}

Local functions close over their surrounding environment

You can create local extension functions:

import atomictest.eq

fun main() {
  fun String.exclaim() = "$this!"
  "Hello".exclaim() eq "Hello!"
}

Some setup for future examples:

package localfunctions

class Session(
  val title: String,
  val speaker: String
}

val sessions = listOf(
  Session("Kotlin Coroutines", "Roman Elizarov"))
    
val favoriteSpeakers = setOf("Roman Elizarov")

You can refer to local function using a function reference:

import localfunctions.*
import atomictest.eq

fun main() {
  fun interesting(session: Session): Boolean {
    if (session.title.contains("Kotlin") &&
      session.speaker in favoriteSpeakers) {
      return true
    }
    
    // ... more checks
    return false
  }
  sessions.any(::interesting) eq true
}

Anonymous functions are defined within other functions but have no name.

Example using an anonymous function:

import localfunctions.*
import atomictest.eq

fun main() {
  sessions.any(
    fun(session: Session): Boolean {
      if (session.title.contains("Kotlin") &&
        session.speaker in favoriteSpeakers) {
        return true
      }
      // ... more checks
      return false
    }
  ) eq true
}

Below forEach acts upon a lambda containing a return:

import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4, 5)
  val value = 3
  var result = ""
  list.forEach {
    result += "$it"
    if (it == value) {
      result eq "123"
      return
    }
  }
  result eq "Never gets here" // [1]
}

A return expression exits a function defined using fun not a lambda. This means [1] is never called.

To return only from a lambda and not from the surrounding function use a labeled return

import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4, 5)
  val value = 3
  var result = ""
  list.forEach {
    result += "$it"
    if (it == value) return@forEach
  }
  return eq "12345"
}

Above the label is the name of the function that called the lambda.

Create a custom label for a lambda like so:

import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4, 5)
  val value = 3
  var result = ""
  list.forEach tag@{
    result += "$it"
    if (it == value) return@tag
  }
}

You can store a lambda or anonymous function in a var or val.

To store a local function use a function reference:

package localfunctions
import atomictest.eq

// Returning an Anonymous function
fun first(): (Int) -> Int {
  val func = fun(i: Int) = i + 1
  func(1) eq 2
  return func
}

//Returning a lambda
fun second(): (String) -> String {
  val func2 = { s: String -> "$s!" }
  func2("abc") eq "abc!"
  return func2
}

//Returning a reference to a local function
fun third(): () -> String {
  fun greet() = "Hi!"
  return ::greet
}

//Returning a reference to a anyonymous function
//Using an expression body
fun fourth() = fun() = "Hi!"

//Returning a lambda using an expression body
fun fifth() = { "Hi!" }

fun main() {
  val funRef1: (Int) -> Int = first()
  val funRef2: (String) -> String = second()
  val funRef3: () -> String = third()
  val funRef4: () -> String = fourth()
  val funRef5: () -> String = fifth()
  
  funRef1(42) eq 43
  //Alt
  first()(42) eq 43
  funRef2("abc") eq "abc!"
  //Alt
  second()("abc") eq "abc!"
  funRef3() eq "Hi!"
  //Alt
  third()() eq "Hi!"
  funRef4() eq "Hi!"
  //...same
  funRef5() eq "Hi!"
  //..same
}

Folding Lists

fold() combines all elements of a list, in order, to generate a single result

Example;

import atomictest.eq

fun main() {
  val list = listOf(1, 10, 100, 1000)
  list.fold(0) { sum, n ->
    sum + n
  } eq 1111
}

0 here is the initial value

foldRight processes elements starting from rightToLeft()

import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c', 'd')
  list.fold("*") { acc, elem ->
    "($acc) + elem"
  } eq ((((*) + a) + b) + c) + d"
  list.foldRight("*") { elem, acc ->
    "$elem + ($acc)"
  } eq "a + (b + (c + (d + (*))))"
}

Use reduce() or reduceRight() like fold() but the initial value is taken from the list

runningFold() and runningReduce() produce a List of all intermediate steps of the process:

import atomictest.eq

fun main() {
  val list = listOf(1, 2, 3, 4)
  list.fold(0) { sum, n ->
    sum + n
  } eq 10
  
  list.runningFold(0) { sum, n ->
    sum + n
  } eq "[0, 1, 3, 6, 10]"
  
  list.reduce { sum, n -> sum + n } eq 10
  
  list.runningReduce { sum, n ->
    sum + n
  } eq "[1, 3, 6, 10]
}

Recursion

Recursion is a technique of calling a function within that same function.

Tail recursion is an optimization that can be explicitly applied to some recursive functions.

A recursive function uses the result of the previous recursive call

package recursion
import atomictest.eq

fun factorial(n: Long): Long {
  if (n <= 1) return 1
  return n * factorial(n - 1)
}

fun main() {
  factorial(5) eq 120
}

Recursive functions are expensive because of a large call stack.

Using tailrec successfully is based on if :

Example for sum

package tailrecursion
import atomictest.eq

private tailrec fun sum(
  n: Long,
  accumulator: Long
): Long =
  if (n == 0L) accumulator
  else sum(n - 1, accumulator + n)
  
fun sum(n: Long) = sum(n, 0)

fun main() {
  sum(2) eq 3
}

Example of fibonacci sequence before and after tailrec

package slowfibonacci
import atomictest.eq

fun fibonacci(n: Long): Long {
  return when(n) {
    0L -> 0
    1L -> 1
    else ->
      fibonacci(n - 1) + fibonacci(n - 2)
  }
}

fun main() {
  fibonacci(0L) eq 0L
  fibonacci(22L) eq 17711L
}
package recursion
import atomictest.eq

fun fibonacci(n: Int): Long {
  tailrec fun fibonacci(
    n: Int,
    current: Long,
    next: Long
  ): Long {
    if (n == 0) return current
    return fibonacci(n - 1, next, current + next)
  }
  return fibonacci(n, 0L, 1L)
}

fun main() {
  (0..8).map { fibonacci(it) } eq
    "[0L, 1L, 1L, 2L, 3L, 5L, 8L, 13L, 21L]"
  fibonacci(22) eq 17711L
}