404 Not Found

404 Not Found


nginx

Kotlin Functions and Lambda Expressions

Functions are first-class citizens in Kotlin — higher-order functions + lambdas + inline let Charlie build OrderProcessor's data processing pipelines in a functional style, reducing code by 60%.

1. What You'll Learn


2. A Developer's Real Story

(1) Pain Point: Duplicate Order Processing Logic

In Charlie's team, every developer wrote their own order filtering/transformation/aggregation logic — 100+ lines of duplicate code scattered across 5 services. Changing one filter condition meant changing 5 places.

(2) Higher-Order Functions Eliminate Duplication

KOTLIN
// Generic order processor - one function replaces 5 copies
fun <T> processOrders(
    orders: List<Order>,
    filter: (Order) -> Boolean,
    transform: (Order) -> T
): List<T> = orders.filter(filter).map(transform)

// Usage: each call site is 2 lines instead of 20
val highValueIds = processOrders(orders, { it.total > 1_000 }, { it.id })
val vipCustomers = processOrders(orders, { it.total > 10_000 }, { it.customer })

Higher-order functions parameterize "what changes" so "what stays the same" is written only once.


3. Function Declarations

(1) Basic Functions

KOTLIN
// Block body
fun calculateTax(total: Double, rate: Double = 0.08): Double {
    return total * rate
}

// Expression body
fun calculateTax(total: Double, rate: Double = 0.08) = total * rate

// Unit return (void)
fun logOrder(order: Order) {
    println("Processing ${order.id}")
}

(2) Default and Named Parameters

KOTLIN
// Default parameters - eliminates method overloading
fun createOrder(
    id: String,
    total: Double,
    status: String = "PENDING",
    currency: String = "USD",
    taxRate: Double = 0.08
): Order = Order(id, total, status, currency)

// Named arguments - call any parameter by name
val order1 = createOrder("ORD-001", 299.99)
val order2 = createOrder("ORD-002", 1_500.00, taxRate = 0.10)
val order3 = createOrder("ORD-003", 45.50, currency = "EUR", status = "CONFIRMED")

(3) Default Parameters vs Java Method Overloading

Dimension Java Overloading Kotlin Default Parameters
Code size N methods 1 method
Maintainability Change N places for one param Change one place
Call flexibility Positional order only Named args, any combination
Interop Java calls directly Needs @JvmOverloads

4. Higher-Order Functions

Higher-order functions take functions as parameters or return functions — the cornerstone of Kotlin's functional programming.

(1) Function Types

KOTLIN
// Function type syntax
val transform: (Order) -> String = { order -> order.id }
val predicate: (Order) -> Boolean = { it.total > 1_000 }
val aggregator: (Double, Order) -> Double = { acc, order -> acc + order.total }
val supplier: () -> Order = { Order("DEFAULT", 0.0, "PENDING") }

// Function as parameter
fun processOrders(orders: List<Order>, transform: (Order) -> String): List<String> {
    return orders.map(transform)
}

(2) Function as Return Value

KOTLIN
// Return a function
fun getDiscountStrategy(tier: String): (Double) -> Double = when (tier) {
    "VIP" -> { total -> total * 0.80 }       // 20% off
    "GOLD" -> { total -> total * 0.90 }      // 10% off
    else -> { total -> total }               // No discount
}

val discount = getDiscountStrategy("VIP")
val finalPrice = discount(1_000.00)  // 800.0

(3) Higher-Order Function + Lambda Call Chain

100%
flowchart LR
    A[Orders] --> B[filter]
    B --> C[map]
    C --> D[sortedBy]
    D --> E[fold]
    E --> F[Result]

5. Lambda Expressions

(1) Lambda Syntax

KOTLIN
// Full syntax
val total = orders.map({ order: Order -> order.total })

// Type inference
val total = orders.map({ order -> order.total })

// it keyword for single parameter
val total = orders.map({ it.total })

// Trailing lambda convention
val total = orders.map { it.total }

// Multi-parameter lambda
val result = orders.fold(0.0) { acc, order -> acc + order.total }

(2) Lambda Syntax Simplification Levels

Level Syntax Description
Full { order: Order -> order.total } Explicit type
Simplified 1 { order -> order.total } Type inference
Simplified 2 { it.total } Single param with it
Trailing lambda orders.map { it.total } Lambda outside parentheses

(3) Common Lambda Patterns

KOTLIN
// let: transform and consume
val formatted = order.total.let { "\$$it USD" }

// also: side effect without changing value
val order = createOrder("ORD-001", 299.99).also {
    println("Created order: ${it.id}")
}

// apply: configure object
val order = Order("ORD-001", 0.0, "PENDING").apply {
    // Can only be used with var properties
}

// run: execute block and return result
val summary = order.run {
    "Order $id: \$${total} USD ($status)"
}

6. Trailing Lambda Convention

When a function's last parameter is a lambda, it can be written outside the parentheses — this is Kotlin's most important syntactic sugar.

(1) Basic Usage

KOTLIN
// Definition
fun processOrders(orders: List<Order>, callback: (Order) -> Unit) { ... }

// Call: trailing lambda
processOrders(orders) { order ->
    println(order.id)
}

// If lambda is the ONLY parameter, omit parentheses
orders.forEach { println(it.id) }

(2) Trailing Lambda as the Foundation of DSLs

KOTLIN
// This is how DSLs work
fun order(id: String, init: OrderBuilder.() -> Unit): Order {
    val builder = OrderBuilder(id)
    builder.init()
    return builder.build()
}

// Usage: trailing lambda makes it read like configuration
val myOrder = order("ORD-001") {
    total(299.99)
    status("CONFIRMED")
    customer("Alice")
}

7. Inline Functions

Lambdas compile to anonymous class objects on the JVM, creating new instances every call. inline eliminates this overhead.

(1) inline Basics

KOTLIN
// Without inline: Lambda creates an object every call
fun process(order: Order, block: (Order) -> String): String {
    return block(order)
}

// With inline: Lambda code is inlined at call site
inline fun process(order: Order, block: (Order) -> String): String {
    return block(order)
}

(2) inline Effect Comparison

KOTLIN
// After inlining, this:
process(order) { it.id }

// Becomes (conceptually):
run { order.id }

(3) inline vs noinline vs crossinline

Modifier Effect Use Case
inline Lambda code inlined at call site Frequently called lambdas
noinline Prevents inlining (stays as object) Lambda needs to be stored/passed
crossinline Inlined but forbids non-local return Lambda in a different execution context

(4) reified Type Parameters

KOTLIN
// Normal: type is erased at runtime
fun <T> isType(value: Any): Boolean = value is T  // ERROR

// reified: inline preserves type info
inline fun <reified T> isType(value: Any): Boolean = value is T  // OK

isType<String>("Hello")  // true
isType<Int>(42)          // true
isType<Int>("42")        // false

8. Complete Example: OrderProcessor Functional Pipeline

KOTLIN
// ============================================
// OrderProcessor - Functional Pipeline
// Feature: Process orders with higher-order functions
// ============================================

data class Order(val id: String, val total: Double, val status: String, val customer: String)

// Generic pipeline processor
inline fun <T, R> List<T>.pipeline(
    filter: (T) -> Boolean,
    transform: (T) -> R,
    crossinline postProcess: (R) -> Unit
): List<R> {
    return this.filter(filter).map(transform).also { results ->
        results.forEach(postProcess)
    }
}

// Discount strategy factory
fun getDiscountStrategy(tier: String): (Double) -> Double = when (tier) {
    "VIP" -> { total -> total * 0.80 }
    "GOLD" -> { total -> total * 0.90 }
    "SILVER" -> { total -> total * 0.95 }
    else -> { total -> total }
}

fun main() {
    val orders = listOf(
        Order("ORD-001", 299.99, "CONFIRMED", "Alice"),
        Order("ORD-002", 15_000.00, "CONFIRMED", "Bob"),
        Order("ORD-003", 2_500.00, "PENDING", "Charlie"),
        Order("ORD-004", 45.50, "CANCELLED", "Alice"),
        Order("ORD-005", 8_000.00, "CONFIRMED", "Bob")
    )

    // Pipeline: filter confirmed -> apply VIP discount -> log
    val discount = getDiscountStrategy("VIP")
    val processed = orders.pipeline(
        filter = { it.status == "CONFIRMED" },
        transform = { it.copy(total = discount(it.total)) },
        postProcess = { println("  Processed: ${it.id} -> \$${it.total} USD") }
    )

    // Aggregate results
    val totalRevenue = processed.sumOf { it.total }
    val avgOrder = processed.map { it.total }.average()
    println("\nRevenue: \$$totalRevenue USD | Avg: \$${"%.2f".format(avgOrder)} USD")

    // Functional composition with let/also/run
    val report = orders
        .filter { it.status != "CANCELLED" }
        .groupBy { it.customer }
        .mapValues { (_, list) ->
            list.sumOf { it.total }
        }
        .also { println("\nRevenue by customer:") }
        .map { (customer, total) -> "$customer: \$$total USD" }
        .joinToString("\n  ")

    println("  $report")
}

Output:

TEXT
  Processed: ORD-001 -> $239.992 USD
  Processed: ORD-002 -> $12000.0 USD
  Processed: ORD-005 -> $6400.0 USD

Revenue: $18639.992 USD | Avg: $6213.330666666667 USD

Revenue by customer:
  Alice: $299.99 USD
  Bob: $23000.0 USD
  Charlie: $2500.0 USD

❓ FAQ

Q What's the difference between a lambda and an anonymous function?
A Lambdas use it as the implicit parameter and return behaves differently (lambda return is non-local, anonymous function return is local). Use lambdas in most cases.
Q When should I use it vs explicit naming?
A Use it when the lambda body is 1-2 lines and context is clear; use explicit names for longer bodies or nested lambdas to improve readability.
Q Should all higher-order functions be inline?
A No. inline is best for small function bodies where the lambda is directly called. Inlining large functions increases code size. Most standard library higher-order functions are inline.
Q Can reified be used in regular classes?
A No. reified only works on type parameters of inline functions, because inlining allows the compiler to preserve type information at the call site. Regular function type parameters are erased at runtime.
Q Is the trailing lambda convention just syntactic sugar?
A It is syntactic sugar but critically important — it's the foundation of Kotlin DSL construction. Gradle Kotlin DSL, Compose UI, Anko, etc. all depend on this convention.
Q How do I choose between let/also/apply/run?
A let for transforming values, also for side effects, apply for configuring objects (returns this), run for executing a block (returns result). Remember: apply returns this, run returns the lambda result.

📖 Summary


📝 Exercises

  1. Beginner (⭐): Write a calculateTotal function with default parameters and call it with named arguments. Hint: fun calc(price: Double, tax: Double = 0.08)
  2. Intermediate (⭐⭐): Implement processAndTransform with higher-order functions, accepting a filter condition and a transform function, returning a result list. Hint: <T> fun List<T>.process...
  3. Challenge (⭐⭐⭐): Implement a filterByType<Any> function that filters elements of a specified type from a mixed-type list. Hint: inline fun <reified T> List<Any>.filterByType(): List<T>

← Previous | Next →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏