I want to create a lambda and assign it to a variable and the following works as expected:
val rollDice = { min: Int, max: Int -> (min..max).random() }
However, when I tried to assign default values to the parameters I got an error:
val rollDice = { min: Int = 1, max: Int = 12 -> (min..max).random() }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Unexpected tokens (use ';' to separate expressions on the same line)
Is it not possible to assign default values to parameters in lambda expressions in Kotlin?
Kotlin Default Argument In Kotlin, you can provide default values to parameters in function definition. If the function is called with arguments passed, those arguments are used as parameters. However, if the function is called without passing argument(s), default arguments are used.
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.
In Kotlin, a function which can accept a function as parameter or can return a function is called Higher-Order function. Instead of Integer, String or Array as a parameter to function, we will pass anonymous function or lambdas. Frequently, lambdas are passed as parameter in Kotlin functions for the convenience.
A lambda expression is a short block of code which takes in parameters and returns a value. Lambda expressions are similar to methods, but they do not need a name and they can be implemented right in the body of a method.
TLDR: Lambda expressions cannot have default parameters. If you need them, you should declare a function (can be locally inside another function).
To elaborate, let's look at different ways how function-like types can be defined in Kotlin. Intuitively, one would expect them to work the same, but there are subtle discrepancies in their functionality.
1. Overloaded functions
When defining function overloads manually (the Java way), it's possible to not just call the function with any allowed argument number, but also store the function reference in a type using any argument number.
fun overload(min: Int, max: Int) = (min..max).random()
fun overload(min: Int) = overload(min, 12)
fun overload() = overload(1, 12)
// Calling is possible with all numbers of arguments, and naming ones at the end
overload()
overload(3)
overload(min=3)
overload(3, 4)
overload(3, max=4)
overload(min=3, max=4)
// Intuitively, all 3 ways of storing work:
val f: (Int, Int) -> Int = ::overload
val g: (Int) -> Int = ::overload
val h: () -> Int = ::overload
// On the other hand, this does NOT compile because of ambiguity:
val i = ::overload
2. Functions with default parameters
More idiomatic in Kotlin is the use of default parameters. While this seems to be mostly equivalent to overloaded functions, it's not. The notable difference is: only a single function is declared, and type inference will consider different argument counts only when calling the function, but not when storing it via function reference.
fun default(min: Int = 1, max: Int = 12) = (min..max).random()
// Calling is possible exactly like overloaded functions
default()
default(3)
default(min=3)
default(3, 4)
default(3, max=4)
default(min=3, max=4)
// No ambiguity, f and g have the same type (all parameters)
val f = ::default
val g: (Int, Int) -> Int = ::default
// However, storing in a function type taking fewer arguments is NOT possible
val h: (Int) -> Int = ::default
val i: () -> Int = ::default
3. Anonymous functions
Anonymous functions allow no default parameters even in the declaration, so there's only one way of calling them. Furthermore, the variable storing them is of function type, which loses information about the parameter names and thus prevents a call with named arguments.
val anonymous = fun(min: Int, max: Int) = (min..max).random()
val anonymous: (Int, Int) -> Int = fun(min: Int, max: Int) = (min..max).random()
// Only one way to call
anonymous(3, 4)
// No ambiguity, f and g have the same (full type)
val f = anonymous
val g: (Int, Int) -> Int = anonymous
// Mistake, which compiles: this declares h as a *property*,
// with type KProperty<(Int, Int) -> Int>
val h = ::anonymous
// Calling with named arguments is NOT possible
anonymous(3, 4) // OK
anonymous(min=3, max=4) // error
4. Lambda expressions
Like anonymous functions, lambda expressions allow no default parameters and cannot be called with named arguments. Since they are stored immediately as a function type like (Int, Int) -> Int
, they undergo the same restrictions as function types referring to actual functions.
Type inference only works if the parameter types are specified either in the lambda expression, or in the function type to assign to:
// OK:
val lambda = { min: Int, max: Int -> (min..max).random() }
val lambda2: (Int, Int) -> Int = { min, max -> (min..max).random() }
// Type inference fails:
val lambda3 = { min, max -> (min..max).random() }
The main takeaway here is that these 4 callables, while supporting the same basic functionality, differ in the following points:
By referring to the callables as function types (which is the only option for anonymous functions and lambdas), you lose information that's present in the original declaration.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With