I can define an inline function in Kotlin with:
inline fun func(a: () -> Unit, b: () -> Unit){
a()
b()
}
But how do I call this function?
For a normal inline function with only one parameter, I would use:
func {
doSomething()
}
Is there a similar syntax for functions with more than one inline parameter?
Only the last function parameter is passed outside the call operator.
For example:
fun foo(a: () -> Unit, b: () -> Unit)
foo({ println("hello") }) {
println("world")
}
The other function parameters are just passed in the normal argument list inside the parenthesis, the last can optionally be moved outside of those parenthesis as is done with most calls.
There are several ways to achieve this.
Probably the nicest is to use a bound function reference for the parameters before the last one:
fun foo(){
func(::foo){foo()}
//this also works:
func(::foo, ::foo)
//or place the other call within parentheses in a lambda. (you can only put the last lambda behind the method call.
func( { foo() } ){foo()}
}
inline fun func(a: () -> Unit, b: () -> Unit){
a()
b()
}
If you want to call an objects method just place that objects name in front of the ::
class Bar {
val baz = Baz()
fun foo() {
func(baz::func2, ::foo)
}
fun func(a: () -> Unit, b: () -> Unit) {
a()
b()
}
}
class Baz{
fun func2(){}
}
Related
package yamin
typealias Foo = () -> Unit
fun main() {
bar {
baz()
}
}
fun bar(foo: Foo) {
foo()
}
context(Foo)
fun baz() {
//
}
I tried to use a lambda type for the context of a function, which seems to be fine at this point but when I tried to call that function in that context, or at least what I think is that context I'm failing, and Kotlin's compiler is showing me this error for baz():
No required context receiver found: Cxt { context((yamin.Foo /* = () -> kotlin.Unit */)) public fun baz(): kotlin.Unit defined in yamin in file Main.kt[SimpleFunctionDescriptorImpl#7b5e305f] }
Maybe I misunderstood Kotlin's context receivers or I'm expecting something that it's not designed for. I just want to create a function that can only be called in certain context and in this example only in a certain lambda.
As it is right now, your baz can be called like this:
val someFoo: Foo = {}
with(someFoo) {
baz()
}
By using with, I bring an instance of () -> Unit into the context, so someFoo becomes a context receiver with which I can call baz. In baz, I can then access this someFoo by using this#Foo. This is how context receivers are supposed to work.
If you want baz to only be able to be called in bar's lambda, bar's lambda needs to provide the context receiver using a receiver parameter, similar to how with's lambda is declared T.() -> R.
object Foo
fun main() {
bar {
baz()
}
}
fun bar(foo: Foo.() -> Unit) {
Foo.foo()
}
context(Foo)
fun baz() {
// ...
}
I changed the actual context here to just an object, because as far as I can see, there is nothing special about bar's lambda. Of course, if you want, you can make it provide extra information to baz by changing Foo to a class that does contain data.
Note that this does not prevent someone from doing:
with(Foo) {
baz()
}
It is quite hard to prevent against this without adding another layer outside of bar, like:
// note that bar is no longer a global function
import bar.Foo.Companion.bar
class Foo private constructor() {
companion object {
fun bar(foo: Foo.() -> Unit) {
Foo().foo()
}
}
}
context(Foo)
fun baz() {
// ...
}
You can also do something simple like this prevents it from happening from outside the package:
sealed interface FooInterface
private object Foo: FooInterface
fun bar(foo: FooInterface.() -> Unit) {
Foo.foo()
}
context(FooInterface)
fun baz() {
// ...
}
To make a function that can only be called in a certain scope, give the function itself a receiver. Instead of typealias Foo = () -> Unit, you would use something like typealias Foo = MyReceiver.() -> Unit.
You can do this with a regular extension receiver, but it's also possible with context receivers.
Here's a simple version that works for me:
typealias Foo = BarScope.() -> Unit
fun main() {
bar {
baz()
}
}
object BarScope
fun bar(foo: Foo) {
BarScope.foo()
}
context(BarScope)
fun baz() {
}
I am trying to do something like the following:
class Event<TPayload>() {
fun subscribe(handler: (payload: TPayload) -> Unit) { ... }
fun subscribe(handler: () -> Unit) where TPayload : Unit { ... }
}
The intention is that instances of Event<Unit> will have two overloads of subscribe(), but other instances will only have one.
The above code will not compile. I tried using extension methods, but would have to use a different name for the extra method, rather than overloading it.
You can define that second function as an extension function so it only appears for Events who have a type of Unit. It's okay to overload the function name. Define it outside the class:
inline fun Event<Unit>.subscribe(crossinline handler: ()->Unit) =
subscribe { handler() }
Test:
class Event<T> {
private val subscribers = mutableListOf<(T)->Unit>()
fun subscribe(handler: (payload: T) -> Unit) {
subscribers += handler
}
fun send(payload: T) {
for (subscriber in subscribers) subscriber(payload)
}
}
fun main() {
val event = Event<Unit>()
// Using verbose syntax to prove it's the extension function being used
// and not a lambda with implicit 'it':
event.subscribe(fun() { println("got unit") })
event.send(Unit)
}
If you use a lambda, the compiler will use the first subscribe function with an implicit it parameter since it takes precedence in overload resolution. But runtime behavior would be the same either way if you aren't using the parameter.
I've looked at the source code of let function:
#kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
i clearly understand that using it keyword inside of the block (code) i'm sending to
let function will refer to the object that called the let function.
But on which object does this keyword will refer if i will use it inside the block (code).
The way i see it, it should be the same object that it refer to, but i'm not sure.
My computer gone bad so i can't test it by myself.
Can someone not only give me the answer but also will explain me the logic behind the this keyword in the situation above?
example of code:
val p:Preson = Person()
p.let{**this**}
this in the let lambda (block) means the same thing as it does outside the let lambda. The let lambda does not suddenly "change" what this means.
For example:
class Foo {
fun foo() {
// here, "this" refers to the receiver of foo(), i.e. some instance of Foo
1.let {
// here, "it" refers to 1,
// and "this" refers to the receiver of foo() too
}
}
}
fun main() {
// here, "this" does not mean anything, main() has no receiver
2.let {
// here, "it" refers to 2,
// and "this" does not mean anything either
}
}
This is because this refers to the receiver of a function, but the function type of the parameter that let takes has no receiver:
public inline fun <T, R> T.let(block: (T) -> R): R {
^^^^^^^^
Compare that to run, which is very similar to let, except its receiver as the receiver of the block parameter:
public inline fun <T, R> T.run(block: T.() -> R): R {
return this.block() // instead of block(this) like let
}
which means that:
1.let {
// here, "this" refers to 1
// "it" does not mean anything because the function type T.() -> R has no parameters
}
Another example that uses both this and it:
fun usesBoth(x: Int, y: String, block: Int.(String) -> Unit) {
x.block(y)
}
If you call usesBoth with a lambda,
usesBoth(1, "x") {
println(this) // prints 1
println(it) // prints x
}
this in the lambda would refer to 1, because block is called on x in usesBoth, and it would refer to "x", because y is passed to block as a parameter.
this is just one of parameters (called a "receiver") that could be passed to the lambda by a calling code. If lambda doesn't support a receiver, as in the let example, this will be just this of the enclosing code:
class Foo {
fun foo() {
val p = Person()
p.let{ this } // `this` is `Foo`
}
}
If lambda has a receiver then it depends what will be passed to it as this. For example, a very similar function to let, but passing the object as this is apply:
class Foo {
fun foo() {
val p = Person()
p.apply{ this } // `this` is `Person`
}
}
Of course, this doesn't have to be the object that we invoked the function on. It could be just any object, it depends on what the function will pass to the lambda. For example, in Kotlin stdlib we have a buildString() function that instantiates a StringBuilder, runs the lambda passing the StringBuilder as this and then returns a string that was built by the lambda. We use this function like this:
val s = buildString {
// `this` is `StringBuilder`
append("hello") // implicit this.append()
append("world")
}
By looking into buildString() signature we can see that the lambda receives StringBuilder as its receiver:
public inline fun buildString(builderAction: StringBuilder.() -> Unit): String {
You can read more about lambdas with receivers in the documentation: https://kotlinlang.org/docs/lambdas.html
Why Kotlin can't reference to suspend function as the parameter of let/also and other functions?
class X
fun a(x: X) {}
suspend fun b(x: X) {}
X().let(::a)
X().let(::b) // Error: Type mismatch
You can only call suspend functions from a coroutine or another suspend function.
And let does not take a suspend function as a parameter.
public inline fun <T, R> T.let(block: (T) -> R): R
So as with any other type, the function declaration has to match. Passing a suspend function to another function that does not accept a suspend function will not work.
It would work when you have a function like:
This is just an example, no real usecase for a suspend function for printing a log!
suspend inline fun log(block: suspend () -> String) {
val message: String = block() // we assume block takes some time to be computed
return println(message) // once its available, we print it
}
You can use the log function like:
suspend fun complexError(): String {
// takes time to compute...
return "message"
}
// usage
suspend fun errorHandling() {
log(::complexError) // pass a reference to complexError()
// or
log() { complexError() }
}
I'm trying to write a function that is essentially a wrapper method around some other functionality, for instance, some logging function.
I've tried several combinations of inline, generic, reified, etc., but nothing seems to work.
My function looks like this:
fun log(note: String, block: () -> Unit): () -> Unit {
print(note)
return block
}
My idea here is to perform some simple operation on the incoming note, and then just return that incoming function to be used as it was originally.
However, I want to do this around overridden functions like so:
override fun onClick(clicked: View) = log("Green Button") {
// here the regular onClick functionality goes
}
Here, I get an error "Return type is () -> Unit, which is not a subtype of overridden". This makes sense enough, as the function signatures do not match.
However, when I do this with other random functions:
fun test() = log("foo") { ... }
fun otherTest(a: String, b: Int) = log("bar") { ... }
I get no errors, and the interpreter somehow seems fine with this. I also tried looking at something like GlobalScope.launch to take that approach, but I couldn't figure it out.
Is what I'm trying to do possible? If not, is there something close?
I think
inline fun log(note: String, block: () -> Unit): Unit {
print(note)
return block()
}
should do what you want. It can be generalized to
inline fun <T> log(note: String, block: () -> T): T {
print(note)
return block()
}
I get no errors, and the interpreter somehow seems fine with this.
Why is that surprising? Those functions just return () -> Unit. If you do e.g.
fun test() = log("foo") { print("bar") }
then calling test() won't print bar; calling test()() will.
Tell me if my understanding is wrong. This is my approach
Extension function:
fun View.onClickWithLog(str: String, l: () -> Unit) {
setOnClickListener { Log.d("LogTag", str); run(l) }
}
Usage (from Activity):
btnTest.onClickWithLog("My Button String"){
Log.d("Actions from Activity", "Content")
finish()
}
and the output is
D/LogTag: My Button String
D/Actions from Activity: Content
which prints your note first, and execute the actions in the lambda expression.
When you use the = operator to assign something to a fun, the expression on the right hand side is supposed to return the return type of that fun
The original fun onClick(clicked:View) : Unit has return type Unit. When you write
override fun onClick(clicked:View) = ... , the ... is what you get when you call onClick(v) so it should be a Unit instead of a View -> Unit (Not even () -> Unit as in your code)
Take a simpler example. Let say you have fun sum(a:Int,b:Int) : Int. When you write override fun sum(a:Int,b:Int) = ... , ... must be an Int instead of a (Int,Int) -> Int since you expect to get an Int immediately when you call sum(a,b). If you somehow got a let say
val someOtherWayToSum : (Int,Int) -> Int = {...}
and want to use it, you can write
override fun sum(a:Int,b:Int) = someOtherWayToSum(a,b)
In your case, you better just do
override fun onClick(clicked:View){
/* some operation (e.g your log)*/
/* the regular onClick functionality */
}
since you are overriding it and implementing its regular functionality right there anyway.