diff --git a/docs/topics/functions.md b/docs/topics/functions.md index 7d231d1b871..a1899b42f3f 100644 --- a/docs/topics/functions.md +++ b/docs/topics/functions.md @@ -3,23 +3,48 @@ Kotlin functions are declared using the `fun` keyword: ```kotlin -fun double(x: Int): Int { - return 2 * x +fun main() { + //sampleStart + // Defines a function that multiplies its parameter by 2 + fun double(number: Int): Int { + return 2 * number + } + + // Calls the function and stores the result + val result = double(2) + println(result) + // Prints: 4 + //sampleEnd } ``` +{kotlin-runnable="true"} ## Function usage Functions are called using the standard approach: ```kotlin -val result = double(2) +fun main() { + //sampleStart + // Defines a simple function + fun double(number: Int): Int { + return 2 * number + } + + // Demonstrates function call syntax + val result = double(2) + println(result) + // Prints: 4 + //sampleEnd +} ``` +{kotlin-runnable="true"} Calling member functions uses dot notation: ```kotlin -Stream().read() // create instance of class Stream and call read() +// Creates an instance of class Stream and calls its read() method +Stream().read() ``` ### Parameters @@ -28,16 +53,31 @@ Function parameters are defined using Pascal notation - *name*: *type*. Paramete parameter must be explicitly typed: ```kotlin -fun powerOf(number: Int, exponent: Int): Int { /*...*/ } +fun main() { + //sampleStart + // Calculates the power of a number using exponentiation + fun powerOf(number: Int, exponent: Int): Int { + return Math.pow(number.toDouble(), exponent.toDouble()).toInt() + } + + val result = powerOf(2, 3) + println(result) + // Prints: 8 + //sampleEnd +} ``` +{kotlin-runnable="true"} You can use a [trailing comma](coding-conventions.md#trailing-commas) when you declare function parameters: ```kotlin +// Declares a function with parameters using trailing comma fun powerOf( number: Int, exponent: Int, // trailing comma -) { /*...*/ } +) { + println("Calculating $number to the power of $exponent") +} ``` ### Default arguments @@ -46,12 +86,32 @@ Function parameters can have default values, which are used when you skip the co of overloads: ```kotlin -fun read( - b: ByteArray, - off: Int = 0, - len: Int = b.size, -) { /*...*/ } +fun main() { + //sampleStart + // Processes text with customizable options + fun processText( + text: String, + upperCase: Boolean = true, + addPrefix: String = "", + ) { + var result = text + if (upperCase) { + result = result.uppercase() + } + println(addPrefix + result) + } + + // Using different combinations of default arguments + processText("hello") + // Prints: HELLO + processText("hello", upperCase = false) + // Prints: hello + processText("hello", addPrefix = "Message: ") + // Prints: Message: HELLO + //sampleEnd +} ``` +{kotlin-runnable="true"} A default value is set by appending `=` to the type. @@ -59,41 +119,101 @@ Overriding methods always use the base method's default parameter values. When overriding a method that has default parameter values, the default parameter values must be omitted from the signature: ```kotlin -open class A { - open fun foo(i: Int = 10) { /*...*/ } -} +fun main() { + //sampleStart + // Defines a base class with a method having default parameter + open class MessageProcessor { + open fun process(message: String = "Default message") { + println("Processing: $message") + } + } + + // Creates a derived class that overrides the process method + class CustomProcessor : MessageProcessor() { + override fun process(message: String) { // No default value is allowed + println("Custom processing: $message") + } + } -class B : A() { - override fun foo(i: Int) { /*...*/ } // No default value is allowed. + // Demonstrates the usage of both classes + val processor = MessageProcessor() + processor.process() // Uses default value + // Prints: Processing: Default message + + val customProcessor = CustomProcessor() + customProcessor.process("Hello") + // Prints: Custom processing: Hello + //sampleEnd } ``` +{kotlin-runnable="true"} If a default parameter precedes a parameter with no default value, the default value can only be used by calling the function with [named arguments](#named-arguments): ```kotlin -fun foo( - bar: Int = 0, - baz: Int, -) { /*...*/ } +fun main() { + //sampleStart + // Configures email settings with default server but required port + fun configureEmail( + server: String = "smtp.default.com", + port: Int, + ) { + println("Configuring email: server=$server, port=$port") + } + + // Calls the function using named argument to specify only the required parameter + configureEmail(port = 587) + // Prints: Configuring email: server=smtp.default.com, port=587 -foo(baz = 1) // The default value bar = 0 is used + // Specifies both parameters using named arguments + configureEmail(server = "smtp.custom.com", port = 465) + // Prints: Configuring email: server=smtp.custom.com, port=465 + //sampleEnd +} ``` +{kotlin-runnable="true"} If the last argument after default parameters is a [lambda](lambdas.md#lambda-expression-syntax), you can pass it either as a named argument or [outside the parentheses](lambdas.md#passing-trailing-lambdas): ```kotlin -fun foo( - bar: Int = 0, - baz: Int = 1, - qux: () -> Unit, -) { /*...*/ } - -foo(1) { println("hello") } // Uses the default value baz = 1 -foo(qux = { println("hello") }) // Uses both default values bar = 0 and baz = 1 -foo { println("hello") } // Uses both default values bar = 0 and baz = 1 +fun main() { + //sampleStart + // Processes data with configurable options and a transformation function + fun processData( + data: String, + uppercase: Boolean = false, + transform: () -> String + ) { + var result = if (uppercase) data.uppercase() else data + result = transform() + println("Result: $result") + } + + // Passes lambda as the last parameter outside parentheses + processData("hello") { + "Transformed text" + } + // Prints: Result: Transformed text + + // Uses named arguments with lambda + processData( + data = "hello", + uppercase = true, + transform = { "TRANSFORMED TEXT" } + ) + // Prints: Result: TRANSFORMED TEXT + + // Uses lambda outside parentheses with default values + processData("hello") { + "Another transformation" + } + // Prints: Result: Another transformation + //sampleEnd +} ``` +{kotlin-runnable="true"} ### Named arguments @@ -103,51 +223,130 @@ arguments and it's difficult to associate a value with an argument, especially i When you use named arguments in a function call, you can freely change the order that they are listed in. If you want to use their default values, you can just leave these arguments out altogether. -Consider the `reformat()` function, which has 4 arguments with default values. +Consider this example of a text formatting function: ```kotlin -fun reformat( - str: String, - normalizeCase: Boolean = true, - upperCaseFirstLetter: Boolean = true, - divideByCamelHumps: Boolean = false, - wordSeparator: Char = ' ', -) { /*...*/ } -``` - -When calling this function, you don't have to name all its arguments: +fun main() { + //sampleStart + // Formats text with various customization options + fun formatText( + text: String, + capitalizeWords: Boolean = false, + addSpaces: Boolean = true, + removeSpecialChars: Boolean = false, + prefix: String = "", + ) { + var result = text + + if (removeSpecialChars) { + result = result.replace(Regex("[^A-Za-z0-9 ]"), "") + } + + if (capitalizeWords) { + result = result.split(" ").map { it.capitalize() }.joinToString(" ") + } + + if (!addSpaces) { + result = result.replace(" ", "") + } + + println(prefix + result) + } -```kotlin -reformat( - "String!", - false, - upperCaseFirstLetter = false, - divideByCamelHumps = true, - '_' -) + // Calls function without named arguments - harder to understand the purpose of each boolean + formatText("hello world!", true, false, true) + // Prints: Hello World + + // Calls function with named arguments - much clearer what each parameter does + formatText( + text = "hello world!", + capitalizeWords = true, + addSpaces = false, + removeSpecialChars = true + ) + // Prints: HelloWorld + + // Uses named arguments to skip some parameters with default values + formatText( + text = "hello world!", + capitalizeWords = true, + prefix = "Result: " + ) + // Prints: Result: Hello World + //sampleEnd +} ``` +{kotlin-runnable="true"} -You can skip all the ones with default values: +After the first skipped argument, you must name all subsequent arguments. This helps maintain code clarity: ```kotlin -reformat("This is a long String!") -``` - -You are also able to skip specific arguments with default values, rather than omitting them all. However, after the first -skipped argument, you must name all subsequent arguments: +fun main() { + //sampleStart + // Formats user data with various options + fun formatUserData( + name: String, + age: Int, + isAdmin: Boolean = false, + showEmail: Boolean = true, + email: String = "" + ) { + println("User: $name, Age: $age" + + (if (isAdmin) " (Admin)" else "") + + (if (showEmail && email.isNotEmpty()) ", Email: $email" else "")) + } -```kotlin -reformat("This is a short String!", upperCaseFirstLetter = false, wordSeparator = '_') + // Uses mix of positional and named arguments + formatUserData("Alice", 25, showEmail = true, email = "alice@example.com") + // Prints: User: Alice, Age: 25, Email: alice@example.com + + // Uses all named arguments in different order + formatUserData( + age = 30, + name = "Bob", + isAdmin = true, + email = "bob@example.com" + ) + // Prints: User: Bob, Age: 30 (Admin), Email: bob@example.com + //sampleEnd +} ``` +{kotlin-runnable="true"} You can pass a [variable number of arguments (`vararg`)](#variable-number-of-arguments-varargs) with names using the `spread` operator: ```kotlin -fun foo(vararg strings: String) { /*...*/ } +fun main() { + //sampleStart + // Processes multiple log messages with a specified log level + fun logMessages(level: String = "INFO", vararg messages: String) { + messages.forEach { message -> + println("[$level] $message") + } + } -foo(strings = *arrayOf("a", "b", "c")) + // Creates an array of messages + val debugMessages = arrayOf("Starting process", "Process completed") + + // Uses named arguments with spread operator + logMessages( + level = "DEBUG", + messages = *debugMessages + ) + // Prints: + // [DEBUG] Starting process + // [DEBUG] Process completed + + // Passes individual messages directly + logMessages("WARNING", "Invalid input", "Process failed") + // Prints: + // [WARNING] Invalid input + // [WARNING] Process failed + //sampleEnd +} ``` +{kotlin-runnable="true"} > When calling Java functions on the JVM, you can't use the named argument syntax because Java bytecode does not > always preserve the names of function parameters. @@ -160,40 +359,129 @@ If a function does not return a useful value, its return type is `Unit`. `Unit` This value does not have to be returned explicitly: ```kotlin -fun printHello(name: String?): Unit { - if (name != null) - println("Hello $name") - else - println("Hi there!") - // `return Unit` or `return` is optional +fun main() { + //sampleStart + // Demonstrates a Unit-returning function with explicit return type + fun greetUser(name: String?): Unit { + if (name != null) { + println("Hello, $name!") + } else { + println("Hello, guest!") + } + // No explicit return needed + } + + // Calls the function with different arguments + greetUser("Alice") + // Prints: Hello, Alice! + greetUser(null) + // Prints: Hello, guest! + //sampleEnd } ``` +{kotlin-runnable="true"} The `Unit` return type declaration is also optional. The above code is equivalent to: ```kotlin -fun printHello(name: String?) { ... } +fun main() { + //sampleStart + // Demonstrates a Unit-returning function without explicit return type + fun greetUser(name: String?) { + if (name != null) { + println("Hello, $name!") + } else { + println("Hello, guest!") + } + } + + // Function behaves the same way + greetUser("Bob") + // Prints: Hello, Bob! + //sampleEnd +} ``` +{kotlin-runnable="true"} ### Single-expression functions When the function body consists of a single expression, the curly braces can be omitted and the body specified after an `=` symbol: ```kotlin -fun double(x: Int): Int = x * 2 +fun main() { + //sampleStart + // Demonstrates a single-expression function with explicit return type + fun calculateArea(width: Int, height: Int): Int = width * height + + // Uses the function to calculate rectangle areas + val smallRectangle = calculateArea(2, 3) + println("Small rectangle area: $smallRectangle") + // Prints: Small rectangle area: 6 + + val largeRectangle = calculateArea(10, 20) + println("Large rectangle area: $largeRectangle") + // Prints: Large rectangle area: 200 + //sampleEnd +} ``` +{kotlin-runnable="true"} Explicitly declaring the return type is [optional](#explicit-return-types) when this can be inferred by the compiler: ```kotlin -fun double(x: Int) = x * 2 +fun main() { + //sampleStart + // Demonstrates a single-expression function with type inference + fun calculateCircleArea(radius: Double) = Math.PI * radius * radius + + // Uses the function with different radius values + val smallCircle = calculateCircleArea(2.0) + println("Small circle area: %.2f".format(smallCircle)) + // Prints: Small circle area: 12.57 + + val largeCircle = calculateCircleArea(5.0) + println("Large circle area: %.2f".format(largeCircle)) + // Prints: Large circle area: 78.54 + //sampleEnd +} ``` +{kotlin-runnable="true"} ### Explicit return types Functions with block body must always specify return types explicitly, unless it's intended for them to return `Unit`, [in which case specifying the return type is optional](#unit-returning-functions). +```kotlin +fun main() { + //sampleStart + // Demonstrates a function requiring explicit return type due to complex logic + fun calculateGrade(score: Int): String { + if (score < 0 || score > 100) { + return "Invalid Score" + } + + return when { + score >= 90 -> "A" + score >= 80 -> "B" + score >= 70 -> "C" + score >= 60 -> "D" + else -> "F" + } + } + + // Tests the function with different scores + println("Score 95: ${calculateGrade(95)}") + // Prints: Score 95: A + println("Score 85: ${calculateGrade(85)}") + // Prints: Score 85: B + println("Score -5: ${calculateGrade(-5)}") + // Prints: Score -5: Invalid Score + //sampleEnd +} +``` +{kotlin-runnable="true"} + Kotlin does not infer return types for functions with block bodies because such functions may have complex control flow in the body, and the return type will be non-obvious to the reader (and sometimes even for the compiler). @@ -202,62 +490,140 @@ in the body, and the return type will be non-obvious to the reader (and sometime You can mark a parameter of a function (usually the last one) with the `vararg` modifier: ```kotlin -fun asList(vararg ts: T): List { - val result = ArrayList() - for (t in ts) // ts is an Array - result.add(t) - return result -} -``` +fun main() { + //sampleStart + // Creates a list from a variable number of elements + fun createList(vararg elements: T): List { + // Converts vararg parameter to list + return elements.toList() + } -In this case, you can pass a variable number of arguments to the function: + // Demonstrates different ways to use vararg parameter + val numbers = createList(1, 2, 3, 4, 5) + println("Numbers: $numbers") + // Prints: Numbers: [1, 2, 3, 4, 5] -```kotlin -val list = asList(1, 2, 3) + val fruits = createList("apple", "banana", "orange") + println("Fruits: $fruits") + // Prints: Fruits: [apple, banana, orange] + //sampleEnd +} ``` +{kotlin-runnable="true"} -Inside a function, a `vararg`-parameter of type `T` is visible as an array of `T`, as in the example above, where the `ts` -variable has type `Array`. - -Only one parameter can be marked as `vararg`. If a `vararg` parameter is not the last one in the list, values for the -subsequent parameters can be passed using named argument syntax, or, if the parameter has a function type, by passing -a lambda outside the parentheses. +Inside a function, a `vararg`-parameter of type `T` is visible as an array of `T`. Only one parameter can be marked as `vararg`. +If a `vararg` parameter is not the last one in the list, values for the subsequent parameters must be passed using named arguments. -When you call a `vararg`-function, you can pass arguments individually, for example `asList(1, 2, 3)`. If you already have -an array and want to pass its contents to the function, use the *spread* operator (prefix the array with `*`): +Here's an example demonstrating more complex usage of varargs: ```kotlin -val a = arrayOf(1, 2, 3) -val list = asList(-1, 0, *a, 4) +fun main() { + //sampleStart + // Processes multiple items with a prefix + fun processItems(prefix: String, vararg items: String) { + items.forEach { item -> + println("$prefix: $item") + } + } + + // Uses vararg with individual arguments + processItems("Item", "A", "B", "C") + // Prints: + // Item: A + // Item: B + // Item: C + + // Uses spread operator with an array + val moreItems = arrayOf("X", "Y", "Z") + processItems("Element", *moreItems) + // Prints: + // Element: X + // Element: Y + // Element: Z + //sampleEnd +} ``` +{kotlin-runnable="true"} -If you want to pass a [primitive type array](arrays.md#primitive-type-arrays) -into `vararg`, you need to convert it to a regular (typed) array using the `toTypedArray()` function: +When working with primitive type arrays, you need to convert them to regular arrays: ```kotlin -val a = intArrayOf(1, 2, 3) // IntArray is a primitive type array -val list = asList(-1, 0, *a.toTypedArray(), 4) +fun main() { + //sampleStart + // Demonstrates working with primitive arrays and varargs + fun sumNumbers(vararg numbers: Int): Int { + return numbers.sum() + } + + // Creates a primitive type array + val primitiveArray = intArrayOf(1, 2, 3) + + // Converts primitive array to regular array and uses spread operator + val sum = sumNumbers(*primitiveArray) + println("Sum: $sum") + // Prints: Sum: 6 + //sampleEnd +} ``` +{kotlin-runnable="true"} ### Infix notation Functions marked with the `infix` keyword can also be called using the infix notation (omitting the dot and the parentheses for the call). Infix functions must meet the following requirements: -* They must be member functions or [extension functions](extensions.md). -* They must have a single parameter. +* They must be member functions or [extension functions](extensions.md) +* They must have a single parameter * The parameter must not [accept variable number of arguments](#variable-number-of-arguments-varargs) and must have -no [default value](#default-arguments). +no [default value](#default-arguments) ```kotlin -infix fun Int.shl(x: Int): Int { ... } +fun main() { + //sampleStart + // Defines a class with an infix function + class PairNumber(val value: Int) { + // Creates an infix function to pair numbers + infix fun pair(other: Int): Pair { + return Pair(this.value, other) + } + } + + // Uses the infix function notation + val number = PairNumber(1) + val pair1 = number pair 2 // Infix notation + val pair2 = number.pair(3) // Standard notation + + println("Infix notation result: $pair1") + // Prints: Infix notation result: (1, 2) + println("Standard notation result: $pair2") + // Prints: Standard notation result: (1, 3) + //sampleEnd +} +``` +{kotlin-runnable="true"} + +Here's another example with a custom string operation: -// calling the function using the infix notation -1 shl 2 +```kotlin +fun main() { + //sampleStart + // Defines an infix extension function for String + infix fun String.addSeparator(other: String): String { + return "$this -> $other" + } + + // Uses the infix function in different ways + val result1 = "Hello" addSeparator "World" // Infix notation + val result2 = "Kotlin".addSeparator("Fun") // Standard notation -// is the same as -1.shl(2) + println(result1) + // Prints: Hello -> World + println(result2) + // Prints: Kotlin -> Fun + //sampleEnd +} ``` +{kotlin-runnable="true"} > Infix function calls have lower precedence than arithmetic operators, type casts, and the `rangeTo` operator. > The following expressions are equivalent: @@ -273,20 +639,38 @@ infix fun Int.shl(x: Int): Int { ... } {style="note"} Note that infix functions always require both the receiver and the parameter to be specified. When you're -calling a method on the current receiver using the infix notation, use `this` explicitly. This is required to ensure -unambiguous parsing. +calling a method on the current receiver using the infix notation, use `this` explicitly: ```kotlin -class MyStringCollection { - infix fun add(s: String) { /*...*/ } - - fun build() { - this add "abc" // Correct - add("abc") // Correct - //add "abc" // Incorrect: the receiver must be specified +fun main() { + //sampleStart + // Defines a collection class with an infix function + class StringCollection { + private val items = mutableListOf() + + // Defines an infix function to add items + infix fun add(item: String) { + items.add(item) + } + + // Demonstrates different ways to call the add function + fun addItems() { + this add "First" // Correct: uses infix notation with explicit receiver + add("Second") // Correct: uses standard notation + // add "Third" // Incorrect: receiver must be specified for infix notation + + println("Items in collection: $items") + } } + + // Creates an instance and adds items + val collection = StringCollection() + collection.addItems() + // Prints: Items in collection: [First, Second] + //sampleEnd } ``` +{kotlin-runnable="true"} ## Function scope @@ -294,52 +678,148 @@ Kotlin functions can be declared at the top level in a file, meaning you do not which you are required to do in languages such as Java, C#, and Scala ([top level definition is available since Scala 3](https://docs.scala-lang.org/scala3/book/taste-toplevel-definitions.html#inner-main)). In addition to top level functions, Kotlin functions can also be declared locally as member functions and extension functions. -### Local functions - -Kotlin supports local functions, which are functions inside other functions: +Here's an example demonstrating different function scopes: ```kotlin -fun dfs(graph: Graph) { - fun dfs(current: Vertex, visited: MutableSet) { - if (!visited.add(current)) return - for (v in current.neighbors) - dfs(v, visited) +fun main() { + //sampleStart + // Top-level function (imagine this at file level) + fun calculateSquare(number: Int): Int { + return number * number + } + + // Class with member function + class Calculator { + private var result: Int = 0 + + // Member function + fun add(number: Int) { + result += number + println("Current result: $result") + } + + // Function with local function + fun calculateSumOfSquares(vararg numbers: Int) { + // Local function that can access outer scope + fun square(n: Int) = n * n + + var sum = 0 + for (number in numbers) { + sum += square(number) // Uses local function + } + println("Sum of squares: $sum") + } } - dfs(graph.vertices[0], HashSet()) + // Demonstrates usage of functions in different scopes + val square = calculateSquare(5) + println("Square of 5: $square") + // Prints: Square of 5: 25 + + val calculator = Calculator() + calculator.add(3) + // Prints: Current result: 3 + calculator.add(7) + // Prints: Current result: 10 + + calculator.calculateSumOfSquares(2, 3, 4) + // Prints: Sum of squares: 29 + //sampleEnd } ``` +{kotlin-runnable="true"} + +### Local functions -A local function can access local variables of outer functions (the closure). In the case above, `visited` can be a local variable: +Kotlin supports local functions, which are functions inside other functions. Local functions can access variables from the outer scope: ```kotlin -fun dfs(graph: Graph) { - val visited = HashSet() - fun dfs(current: Vertex) { - if (!visited.add(current)) return - for (v in current.neighbors) - dfs(v) +fun main() { + //sampleStart + // Demonstrates local functions with access to outer scope + fun processNumbers(numbers: List) { + var count = 0 + + // Local function that can access count from outer scope + fun incrementCountFor(predicate: (Int) -> Boolean) { + for (number in numbers) { + if (predicate(number)) { + count++ + } + } + println("Found $count numbers") + } + + // Uses local function to count even numbers + incrementCountFor { it % 2 == 0 } + // Prints: Found {number of even integers} numbers + + count = 0 // Resets counter + + // Uses local function to count numbers greater than 5 + incrementCountFor { it > 5 } + // Prints: Found {number of integers > 5} numbers } - dfs(graph.vertices[0]) + // Tests the function with a list of numbers + processNumbers(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + //sampleEnd } ``` +{kotlin-runnable="true"} ### Member functions A member function is a function that is defined inside a class or object: ```kotlin -class Sample { - fun foo() { print("Foo") } -} -``` +fun main() { + //sampleStart + // Defines a class representing a bank account + class BankAccount(private var balance: Double) { + // Member function to deposit money + fun deposit(amount: Double) { + if (amount > 0) { + balance += amount + println("Deposited $amount. New balance: $balance") + } else { + println("Invalid deposit amount") + } + } + + // Member function to withdraw money + fun withdraw(amount: Double) { + if (amount > 0 && amount <= balance) { + balance -= amount + println("Withdrawn $amount. New balance: $balance") + } else { + println("Invalid withdrawal amount or insufficient funds") + } + } + + // Member function to check balance + fun checkBalance() { + println("Current balance: $balance") + } + } -Member functions are called with dot notation: + // Creates an account and performs operations + val account = BankAccount(100.0) + account.checkBalance() + // Prints: Current balance: 100.0 -```kotlin -Sample().foo() // creates instance of class Sample and calls foo + account.deposit(50.0) + // Prints: Deposited 50.0. New balance: 150.0 + + account.withdraw(70.0) + // Prints: Withdrawn 70.0. New balance: 80.0 + + account.withdraw(90.0) + // Prints: Invalid withdrawal amount or insufficient funds + //sampleEnd +} ``` +{kotlin-runnable="true"} For more information on classes and overriding members see [Classes](classes.md) and [Inheritance](classes.md#inheritance). @@ -348,8 +828,30 @@ For more information on classes and overriding members see [Classes](classes.md) Functions can have generic parameters, which are specified using angle brackets before the function name: ```kotlin -fun singletonList(item: T): List { /*...*/ } +fun main() { + //sampleStart + // Demonstrates a generic function that creates a list wrapper + class ListWrapper(private val item: T) { + fun getList(): List = listOf(item) + } + + // Generic function to create a ListWrapper + fun wrapInList(item: T): ListWrapper { + return ListWrapper(item) + } + + // Uses the generic function with different types + val numberWrapper = wrapInList(42) + println("Number list: ${numberWrapper.getList()}") + // Prints: Number list: [42] + + val stringWrapper = wrapInList("Hello") + println("String list: ${stringWrapper.getList()}") + // Prints: String list: [Hello] + //sampleEnd +} ``` +{kotlin-runnable="true"} For more information on generic functions, see [Generics](generics.md). @@ -361,28 +863,68 @@ When a function is marked with the `tailrec` modifier and meets the required for the recursion, leaving behind a fast and efficient loop based version instead: ```kotlin -val eps = 1E-10 // "good enough", could be 10^-15 - -tailrec fun findFixPoint(x: Double = 1.0): Double = - if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x)) +fun main() { + //sampleStart + // Calculates factorial using tail recursion + tailrec fun factorial(n: Long, accumulator: Long = 1): Long = + when { + n <= 1 -> accumulator + else -> factorial(n - 1, n * accumulator) + } + + // Tests factorial function with different numbers + println("Factorial of 5: ${factorial(5)}") + // Prints: Factorial of 5: 120 + + println("Factorial of 10: ${factorial(10)}") + // Prints: Factorial of 10: 3628800 + + // Demonstrates a more complex tail recursive function + // Finds the greatest common divisor using Euclidean algorithm + tailrec fun gcd(a: Int, b: Int): Int = + if (b == 0) a + else gcd(b, a % b) + + // Tests GCD function with different number pairs + println("GCD of 48 and 36: ${gcd(48, 36)}") + // Prints: GCD of 48 and 36: 12 + + println("GCD of 125 and 25: ${gcd(125, 25)}") + // Prints: GCD of 125 and 25: 25 + //sampleEnd +} ``` +{kotlin-runnable="true"} -This code calculates the `fixpoint` of cosine, which is a mathematical constant. It simply calls `Math.cos` repeatedly -starting at `1.0` until the result no longer changes, yielding a result of `0.7390851332151611` for the specified -`eps` precision. The resulting code is equivalent to this more traditional style: +The equivalent non-tail-recursive implementation would look like this: ```kotlin -val eps = 1E-10 // "good enough", could be 10^-15 - -private fun findFixPoint(): Double { - var x = 1.0 - while (true) { - val y = Math.cos(x) - if (Math.abs(x - y) < eps) return x - x = Math.cos(x) +fun main() { + //sampleStart + // Non-tail-recursive factorial (less efficient, risk of stack overflow) + fun factorial(n: Long): Long = + if (n <= 1) 1 + else n * factorial(n - 1) // Not tail recursive: multiplication after recursive call + + // Traditional loop-based implementation + fun factorialLoop(n: Long): Long { + var result = 1L + for (i in 1..n) { + result *= i + } + return result } + + // Compares results of different implementations + val n = 5L + println("Tail recursive factorial: ${factorial(n, 1)}") + println("Non-tail recursive factorial: ${factorial(n)}") + println("Loop-based factorial: ${factorialLoop(n)}") + // All print: 120 + //sampleEnd } ``` +{kotlin-runnable="true"} To be eligible for the `tailrec` modifier, a function must call itself as the last operation it performs. You cannot use tail recursion when there is more code after the recursive call, within `try`/`catch`/`finally` blocks, or on open functions. @@ -392,4 +934,3 @@ Currently, tail recursion is supported by Kotlin for the JVM and Kotlin/Native. * [Inline functions](inline-functions.md) * [Extension functions](extensions.md) * [Higher-order functions and lambdas](lambdas.md) - diff --git a/docs/topics/generics.md b/docs/topics/generics.md index 4a17b5ffda1..21edadaf906 100644 --- a/docs/topics/generics.md +++ b/docs/topics/generics.md @@ -378,6 +378,7 @@ arguments at runtime, and the compiler prohibits such `is`-checks such as ```kotlin if (something is List<*>) { something.forEach { println(it) } // The items are typed as `Any?` + // Prints: [value of each item in the list] } ``` @@ -422,9 +423,13 @@ val stringToStringList = somePair.asPairOf>() // Compiles b fun main() { println("stringToSomething = " + stringToSomething) + // Prints: stringToSomething = (items, [1, 2, 3]) println("stringToInt = " + stringToInt) + // Prints: stringToInt = null println("stringToList = " + stringToList) + // Prints: stringToList = (items, [1, 2, 3]) println("stringToStringList = " + stringToStringList) + // Prints: stringToStringList = (items, [1, 2, 3]) //println(stringToStringList?.second?.forEach() {it.length}) // This will throw ClassCastException as list items are not String } ``` diff --git a/docs/topics/idioms.md b/docs/topics/idioms.md index 82523d3d932..40b93eef491 100644 --- a/docs/topics/idioms.md +++ b/docs/topics/idioms.md @@ -49,6 +49,7 @@ if ("jane@example.com" !in emailsList) { ... } ```kotlin println("Name $name") +// Prints: Name [value of name variable] ``` Learn the difference between [Java and Kotlin string concatenation](java-to-kotlin-idioms-strings.md#concatenate-strings). @@ -59,12 +60,12 @@ Learn the difference between [Java and Kotlin string concatenation](java-to-kotl // Reads a string and returns null if the input can't be converted into an integer. For example: Hi there! val wrongInt = readln().toIntOrNull() println(wrongInt) -// null +// Prints: null // Reads a string that can be converted into an integer and returns an integer. For example: 13 val correctInt = readln().toIntOrNull() println(correctInt) -// 13 +// Prints: 13 ``` For more information, see [Read standard input.](read-standard-input.md) @@ -94,6 +95,7 @@ val map = mapOf("a" to 1, "b" to 2, "c" to 3) ```kotlin println(map["key"]) +// Prints: [value associated with "key" in the map] map["key"] = value ``` @@ -102,6 +104,7 @@ map["key"] = value ```kotlin for ((k, v) in map) { println("$k -> $v") + // Prints: [key] -> [value] } ``` @@ -371,7 +374,7 @@ a = b.also { b = a } ``` ## Mark code as incomplete (TODO) - + Kotlin's standard library has a `TODO()` function that will always throw a `NotImplementedError`. Its return type is `Nothing` so it can be used regardless of expected type. There's also an overload that accepts a reason parameter: diff --git a/docs/topics/interfaces.md b/docs/topics/interfaces.md index 85e5543b799..c16090aaa92 100644 --- a/docs/topics/interfaces.md +++ b/docs/topics/interfaces.md @@ -42,6 +42,7 @@ interface MyInterface { fun foo() { print(prop) + // Prints: [value of prop property] } } @@ -64,7 +65,7 @@ interface Named { interface Person : Named { val firstName: String val lastName: String - + override val name: String get() = "$firstName $lastName" } @@ -83,16 +84,20 @@ When you declare many types in your supertype list, you may inherit more than on ```kotlin interface A { fun foo() { print("A") } + // Prints: A fun bar() } interface B { fun foo() { print("B") } + // Prints: B fun bar() { print("bar") } + // Prints: bar } class C : A { override fun bar() { print("bar") } + // Prints: bar } class D : A, B {