Dima Sidukov
Dima Sidukov

Reputation: 69

What exactly is happening in a second call of iterate?

I have the following code in Kotlin:

fun iterate(first: String, second: String, iter: Int, func: (String, String) -> String) {
    for(i in 0..iter)
        println(func(first, second))
}

fun concatenation(one: String, two: String): String {
    return "$one $two";
}

fun main(args: Array<String>) {
    iterate("Me", "You", 5, ::concatenation)
    iterate("Another", "One", 6) {a, b -> a + b}
}

Can anyone explain what's happening in curly brackets in the second call of iterate function? Am I overriding it? Also, I tried to swap func and iter paratemers in function declaration. This way I am not able anymore to use curly brackets in the second call and insert a code, because I don't know where to put iter argument.

Upvotes: 0

Views: 38

Answers (2)

gidds
gidds

Reputation: 18577

Your function iterate() takes four parameters: two Strings, and Int, and a function.

When you call iterate(), you can give the String arguments in several ways: for example, you could give the name of a variable holding the String (such as one); or you could give a literal String value (such as "Me").

And you can give the function argument in both of those ways, too.*

The equivalent of the first way is to give a reference to an existing function.  That's what ::concatenation is.  (:: is used to create function references.)

The equivalent of the second way is to use a lambda, which is a way of writing out the contents of a function.  {a, b -> a + b} is a lambda: it's a function which takes two parameters, and returns their concatenation.  (In this case the compiler can tell from the context that the two parameters must be Strings; in other cases, you might need to specify their types.)

What's tricky about the code in this question is that the lambda doesn't look like it's being passed as an argument to the function!  It's outside the parens:

iterate("Another", "One", 6) {a, b -> a + b}

And this is a feature that's specific to Kotlin: if the last parameter is a function, you can put the lambda after the close paren.  (And, if that's the only argument, you can omit the parens entirely.)

So it means exactly the same as:

iterate("Another", "One", 6, {a, b -> a + b})

This may look confusing, but it supports function calls that look like new language syntax.  For example, you may have used the with() function:

with (someObject) {
    // In here, ‘this’ refers to someObject.
}

That's not a keyword in the language; it's simply a normal function which takes its action as the last parameter.

So I bet you can now see the answer to your last question.  If you were to swap the iter and func params around, you'd have to call it like:

iterate("Another", "One", {a, b -> a + b}, 6)

(Because func is no longer the last parameter, it has to be inside the parens now.)


(* There are additional ways to specify string params and function params, too, but there's no room to go into them all here!)

Upvotes: 1

cactustictacs
cactustictacs

Reputation: 19554

Your func parameter's type is a function - its type is (String, String) -> String which means it takes two String parameters (in the parentheses) and returns a String (after the arrow).

That's what all function types look like, (x) -> y. A function type that takes no parameters and doesn't return a value has a type () -> Unit (because every function returns something in Kotlin, Unit is the "no meaningful value" type).

So for your func parameter, you can pass in any function with that type - takes two strings, returns a string. Your concatenation function matches that, so you can pass it in - you're using a function reference, ::concatenation which is a way to pass that function - it's a reference to it.

With your second iterate call, you're creating a function object to pass in, instead of referring to one that's declared as an actual method on the class. You're using a lambda that takes two parameters, a and b, and calls + on them (and implicitly returns that value as the result).

Now you're not saying that they're Strings here (you could declare their types explicitly if you wanted), but the type system is basically checking that this can apply to two String variables, and that the result of + on those would also be a String. So it all works fine, and it can be used in your iterate function.


Like @IR42 says in the comments, you're using a trailing lambda - basically when the last parameter in a function call is a lambda, instead of keeping it in the parentheses, you can move it outside (and remove the parentheses if the lambda was the only thing in there).

But this only works when it's the last parameter - otherwise it wouldn't know for sure which one you'd moved out! - which is why you can't change the order and keep the trailing lambda. You can switch up the order, but the call will have to be like this:

iterate("Me", "You", ::concatenation, 5)
iterate("Another", "One", {a, b -> a + b}, 6)

Upvotes: 1

Related Questions