Understanding a lambda construct that contains dot followed by brackets - kotlin

This is the function declaration for rememberCoilPainter:
#Composable
fun rememberCoilPainter(
request: Any?,
imageLoader: ImageLoader = CoilPainterDefaults.defaultImageLoader(),
shouldRefetchOnSizeChange: ShouldRefetchOnSizeChange = ShouldRefetchOnSizeChange { _, _ -> false },
requestBuilder: (ImageRequest.Builder.(size: IntSize) -> ImageRequest.Builder)? = null,
fadeIn: Boolean = false,
fadeInDurationMs: Int = LoadPainterDefaults.FadeInTransitionDuration,
#DrawableRes previewPlaceholder: Int = 0,
): LoadPainter<Any> {
}
The line of code I am having difficulty understanding is:
requestBuilder: (ImageRequest.Builder.(size: IntSize) -> ImageRequest.Builder)? = null
A dot appears after Builder followed by (size: IntSize)
This is the first time I've seen this construct in Kotlin and am not sure how to interpret it. This is a lambda. Normally the dot after an object refers to a sub component of a class or a package. But the ( ) after the dot isn't clear.
How do I implement the requestBuilder parameter?

This is a function with receiver type as described here: https://kotlinlang.org/docs/lambdas.html#function-types
Function types can optionally have an additional receiver type, which is specified before a dot in the notation: the type A.(B) -> C represents functions that can be called on a receiver object of A with a parameter of B and return a value of C. Function literals with receiver are often used along with these types.
It could be tricky to understand at first, but this is like you are providing a function/lambda that is a method of ImageRequest.Builder. Or in other words: your lambda receives one additional parameter of type ImageRequest.Builder and it is available in the lambda as this.
You can provide requestBuilder as any other lambda, but note that inside it you will have access to properties and methods of ImageRequest.Builder object that was provided to you.

What you are looking at is a "function literal with receiver". Speaking generically, a type A.(B) -> C represents a function that can be called on a receiver object of A with a parameter of B and return a value of C. Or in your example:
requestBuilder: (ImageRequest.Builder.(size: IntSize) -> ImageRequest.Builder)?
We have a function requestBuilder which can be called on a ImageRequest.Builder with a parameter size: IntSize and returns another ImageRequest.Builder.
Calling this function is just like calling any other function with a lambda as a parameter. The difference: You have access to ImageRequest.Builder as this inside your lambda block.

Hope the following example helps understand lambdas with receiver type:
data class Person(val name: String)
fun getPrefixSafely(
prefixLength: Int,
person: Person?,
getPrefix: Person.(Int) -> String): String
{
if (person?.name?.length ?: 0 < prefixLength) return ""
return person?.getPrefix(prefixLength).orEmpty()
}
// Here is how getPrefixSafely can be called
getPrefixSafely(
prefixLength = 2,
person = Person("name"),
getPrefix = { x -> this.name.take(x) }
)
How do I implement the requestBuilder parameter?
Hope this part of the code snippet answers the above:
getPrefix = { x -> this.name.take(x) }
PS: These lambdas with receiver types are similar to extension functions IMO.

Related

Kotlin - Passing Trailing Lambdas- Function with two Parameter as Parameter

I am trying to learn function as parameter. My example could be false but in purpose of learning I try to do code below.
I am getting
Type mismatch. Required: (Int, String) → String Found: () → String
What I try is to create "showNameAndAge" with two parameter(String and Int) required and pass it parameter in myMessage Func.
fun main() {
myMessage(message = "Hello",1){
showNameAndAge(2,"it")
}
}
val showNameAndAge:(Int,String)->String={a,b-> "hello $a and $b" }
fun myMessage(message:String,a:Int,funAsParameter2:(Int,String)->String){ //trailing lamda
println("$message + $a ${funAsParameter2(a,message)}")
}
Your code is almost correct, but you need to provide names for parameters received inside the lambda:
myMessage(message = "Hello",1){ p1, p2 ->
showNameAndAge(2,"it")
}
If you don't use these arguments inside lambda, you can use _ as their names:
myMessage(message = "Hello",1){ _, _ ->

How to skip specification of the generic type parameter in Kotlin?

This is the main body of my function
val client = ConnectionFactory.createClient() # <- Return lettice.io RedisClusterClient
val conn = client.connect()
val command = conn.sync()
var index: String? = null
index = readDataStructure(command, key)
This is my first try to define my readDataStructure function:
fun readDataStructure(command: RedisCommand, key: String): String {
...
kotlin complaints error: 3 type arguments expected for interface RedisCommand<K : Any!, V : Any!, T : Any!>
I want to be able to NOT specifying K, V and T because I am just writing a throwaway script.
Is there any Kotlin lang syntax and can allow me to just pass the command variable as is?
I suppose you are after:
fun readDataStructure(command: RedisCommand<*,*,*>, key: String): String {
?
From Kotlin docs https://kotlinlang.org/docs/tutorials/kotlin-for-py/generics.html:
If you don't have any idea (or don't care) what the generic type might be, you can use a star-projection:
fun printSize(items: List<*>) = println(items.size)
When using a generic type where you have star-projected one or more of its type parameters, you can:
Use any members that don't mention the star-projected type parameter(s) at all
Use any members that return the star-projected type parameter(s), but the return type will appear to be Any? (unless the type parameter is constrained, in which case you'll get the type mentioned in the constraint)
Not use any members that take a star-projected type as a parameter

kotlin object conversion in lambdas convert

I'm trying to have this compiling:
val criteriaList = aList.stream().map { dateRange -> {
Criteria.where("KEY").`is`(dateRange) } }.toList().toTypedArray()
Criteria().orOperator(*criteriaList)
But:
Criteria().orOperator(*criteriaList)
Currently does not compile:
Type mismatch.
Required:
Array<(out) Criteria!>!
Found:
Array<(() → Criteria!)!>
Why?
You are mapping your dateRange to a () -> Criteria.
You do not need to wrap what is following after -> with curly braces. Check also the Kotlin reference regarding Lambda expression syntax:
val sum = { x: Int, y: Int -> x + y }
A lambda expression is always surrounded by curly braces [...], the body goes after an -> sign. If the inferred return type of the lambda is not
Unit, the last (or possibly single) expression inside the lambda body is treated as the return value.
So you could just write the following instead:
.map { dateRange -> Criteria.where("KEY").`is`(dateRange) }
Note also that you do not really need to call stream(), but you can directly call map on it (except it wouldn't be a real List in the first place).
So your code could probably be simplified to something like:
val criteriaList = aList.map { dateRange -> Criteria.where("KEY").`is`(dateRange) }
.toTypedArray()
or
val criteriaList = aList.map { Criteria.where("KEY").`is`(it) }
.toTypedArray()

Kotlin methods with Vararg as First Parameter

Note I've looked at the following questions/answers to solve the problem without any luck. Call Java Varargs Method from Kotlin - this one has the varargs parmeter at the end of the parameter list, but my question deals with varargs at the start of the parameters list. Kotlin: Convert List to Java Varargs - the same. Other searches yield the same thing. These were the closest I could find.
I am calling the Kotlin String.split method with a single character delimiter.
This is a vararg method where the vararg parameter is first of multiple parameters. The method is defined like so:
public fun CharSequence.split(vararg delimiters: Char,
ignoreCase: Boolean = false,
limit: Int = 0): List<String>
When I call the method as below, it compiles fine:
fun String.splitRuleSymbol() : String = this.split(':') //ok
But when I try to add the ignoreCase and limit parameters, I get a problem:
fun String.splitRuleSymbol() : String = this.split(':', true, 2) //compiler error
The error I get is...
None of the following functions can be called with the arguments supplied:
public fun CharSequence.split(vararg delimiters: String, ignoreCase: Boolean = ..., limit: Int = ...): List defined in kotlin.text
public fun CharSequence.split(vararg delimiters: Char, ignoreCase: Boolean = ..., limit: Int = ...): List defined in kotlin.text
To me, having a vararg parameter followed by other parameters is somewhat odd, but that's beside the point. If I call it as below, it works fine:
// both of the following compile
fun String.splitRuleSymbol() : String =
this.split(delimiters = ':', ignoreCase = true, limit = 2)
fun String.splitRuleSymbol2() : String =
this.split(';', ignoreCase = true, limit = 2)
Is there a way to pass a vararg Char in to this method without having to qualify my other two parameters with parameter names ignoreCase and limit? Can the compiler not tell that the remaining parameters are not Char?
I have tried the spread operator and a few other ways below , none of which work:
//compiler errors on all these
this.split(*':', true, 2) //using the "spread" operator
this.split(*charArrayOf(':'), true, 2)
this.split(*mutableListOf(':'), true, 2)
this.split(*Array<Char>(1) { ':' }, true, 2)
Yes, some of these look ridiculous, I know. But, is there no way to avoid the verbose alternative?
PS As I was formulating my question, I found another expression that compiled.
this.split(':', limit = 2)
This is less verbose and since I don't need to change the default ignoreCase parameter, it's closer to what I am looking for.
Your observations are correct. Arguments that are after a vararg parameter can only ever be passed in by using named arguments, otherwise you'd run into ambiguity issues (for a trivial example, let's say when all arguments are of type Any).
The best source I can find for this right now is this book.
The vararg parameter is usually the last parameter, but it does not always have to be. If there are other parameters after vararg, then arguments must be passed in using named parameters
Edit: #Les found a good source on it, see their answer.
Thanks to zsmb13, I was able to find the following paragraph in the Kotlin Specification (under "Functions and Lambdas")
Only one parameter may be marked as vararg . If a vararg parameter is
not the last one in the list, values for the following parameters can
be passed using the named argument syntax, or, if the parameter has a
function type, by passing a lambda outside parentheses.
I would venture to add that "can be passed" should be changed to "must be passed" since the compiler won't allow otherwise.
Note The lambda part is interesting in that the spec normally only allows a lambda to be moved outside the parenthesis when it is the last parameter. The wording of the spec implies the lambda could be anywhere after the vararg parameter, but experimentation shows that it cannont, i.e., it must be the last parameter in order to be eligible to move outside of the parenthesis.
fun main(args: Array<String>) {
test("hello", limit = 1, ic = false, delims = ';') { } //ok
//test2("world", limit = 1, ic = false, delims = ';') { } //error
test2("world", f = {}, limit = 1, ic = false, delims = ';') //ok
test("hello world", ';', limit = 1, ic = false) {} //ok
}
fun test(vararg delims: Char, ic: Boolean, limit: Int, f: () -> Unit) {}
fun test2(vararg delims: Char, f: () -> Unit, ic: Boolean, limit: Int) {}
Variable number of arguments (vararg) can be passed in the named form by using the spread operator:
fun foo(vararg strings: String) { /* ... */ }
foo(strings = *arrayOf("a", "b", "c"))
foo(strings = "a") // Not required for a single value
Note that the named argument syntax cannot be used when calling Java functions, because Java bytecode does not always preserve names of function parameters.

What does "with" mean in Kotlin?

I've read the docs on it 3 times and I still have no idea what it does. Can someone ELI5 (Explain Like I'm Five) it please? Here's how I'm using it:
fun main(args: Array<String>) {
val UserModel = UserModel()
val app = Javalin.create().port(7000).start()
with (app) {
get("/users") {
context -> context.json(UserModel)
}
}
}
with is used to access an object's members and methods without having to refer to the object once per access. It is (mostly) for abbreviating your code. It is frequently used when constructing an object:
// Verbose way, 204 characters:
var thing = Thingummy()
thing.component1 = something()
thing.component2 = somethingElse()
thing.component3 = constantValue
thing.component4 = foo()
thing.component5 = bar()
parent.children.add(thing)
thing.refcount = 1
// Terse way, 182 characters:
var thing = Thingummy()
with(thing) {
component1 = something()
component2 = somethingElse()
component3 = constantValue
component4 = foo()
component5 = bar()
parent.children.add(this)
refcount = 1
}
The documentation says:
inline fun <T, R> with(receiver: T, block: T.() -> R): R (source)
Calls the specified function block with the given receiver as its receiver and returns its result.
The way I think of it is that it is calling a function (the block) where this in the scope of the block is the receiver.
Whatever the block returns is the return type.
Essentially calling a method where you provide the implicit this and can return any result from it.
Here is an example to demonstrate:
val rec = "hello"
val returnedValue: Int = with(rec) {
println("$this is ${length}")
lastIndexOf("l")
}
The rec in this case is the receiver of the function call - the this in the scope of the block. The $length and lastIndexOf are both called on the receiver.
The return value can be seen to be an Int because that is the last method call in the body - that is the generic type parameter R of the signature.
The definition of with:
inline fun <T, R> with(receiver: T, block: T.() -> R): R (source)
Actually it's implementation is straight forward: The block is executed on receiver, which works for any type:
receiver.block() //that's the body of `with`
The great thing to mention here, is the parameter type T.() -> R:
It's called function literal with receiver. It's actually a lambda that can access the receiver's members without any additional qualifiers.
In your example the context of with receiver app is accessed in that way.
Besides stdlib functions like with or apply, this functionality is what makes Kotlin great for writing Domain Specific Languages as it allows the creation of scopes within which you have access on certain functionalities.
val citizen2 = Citizen("Tom", 24, "Washington")
val age = with(citizen2) {
println("$name - $age $residence ")
age = this.age + age
residence = "Florida"
age+10 // returns 58
}
println("${citizen2.name} - ${citizen2.age} - $age - ${citizen2.residence} ")
data class Citizen(var name: String, var age: Int, var residence: String)
Output:
Tom - 24 Washington
Tom - 48 - 58 - Florida
Note that :
We can access age property of citizen(receiver object) with this.age or age
last line(age+10 in this example) in the lambda of with() returns.
With is used to apply several operations to an object or access object's methods e.g. in this example we are accessing String's capitalize() extension method
data class Person(val name:String)
fun main(){
val person = Person("john doe")
with(person) {
println(name.capitalize()) // output John Doe
}
}
Under the hood with is a higher-order function. Here we are saying with the Person name call capitalize ( ). We don’t actually need ‘this’ because it is implicit and can be removed