https://kotlinlang.org logo
#getting-started
Title
# getting-started
t

Tgo1014

11/10/2023, 9:36 AM
What’s the difference between extending from a lambda and overriding the invoke operator? Is there any reason why I would use one over another?
Copy code
class A : () -> String {
    override fun invoke() = "A"
}
class B {
    operator fun invoke() = "B"
}
A()()
B()()
s

Sam

11/10/2023, 9:41 AM
One difference is that
A
can be assigned directly to
() -> String
but
B
can't:
Copy code
fun foo(bar: () -> String) = bar()

foo(A()) // ✅
foo(A()::invoke) // ✅
foo(B()) // ❌
foo(B()::invoke) // ✅
I'd say: • Use the operator function when you just want to be able to easily use function call syntax in your code for ergonomic reasons • Extend from the function type when you actually need to use the object polymorphically in place of a function reference
t

Tgo1014

11/10/2023, 9:46 AM
Thanks @Sam! I had no idea about this. I think for my case (using just as
instance()
) it doesn’t make any difference.
👍 1
e

ephemient

11/10/2023, 9:52 AM
Copy code
{ "C" }()
(fun() = "D")()
🔠 1
🤯 3
although there isn't any need for an IIFE in Kotlin, you can use
run {}
if you just want a temporary scope
also generally I'd find it a bit easier to write
Copy code
fun makeALambda(arg: String) = { "A: $arg" }
than to write a
class
with
override fun invoke()
, although you do need a real class (or anonymous object) if you need to make the lambda self-referential
t

Tgo1014

11/10/2023, 9:58 AM
For my case is just invoking a usecase that’s injected, so it’s not every complicated. The “D” case make my head hurts, I have no idea what’s this 🤣
e

ephemient

11/10/2023, 10:00 AM
it's just another way to write a lambda expression. sometimes it's more convenient, and it's the only way to give the lambda an explicit return type
Copy code
{ a: Int, b: Int -> "($a, $b)" }
(fun(a: Int, b: Int): String = "($a, $b)")
(fun(a: Int, b: Int): String {
    return "($a, $b)"
})
are equivalent
😵 1
y

Youssef Shoaib [MOD]

11/10/2023, 12:07 PM
@ephemient
you do need a real class (or anonymous object) if you need to make the lambda self-referential
Well...
Copy code
fun main() {
    val factorial = makeFactorialStartingFrom(1)
   println(factorial(5))
}

fun makeFactorialStartingFrom(start: Int): (Int) -> Int = fix { fact, int -> 
    if(int == 0) start else int * fact(int - 1) 
}

fun <T, R> fix(f: ((T) -> R, T) -> R): (T) -> R = { f(fix(f), it) }
I think theoretically lambdas can do everything that classes can, except participate in class or interface hierarchies obviously.
e

ephemient

11/10/2023, 4:20 PM
that's not really referring to itself, it's getting a different lambda passed in, so
Copy code
val f = fix { f, _ ->
    unregisterCallback(f)
}
registerCallback(f)
doesn't work
y

Youssef Shoaib [MOD]

11/11/2023, 5:36 AM
That's true. I think this version gets the same function passed in everytime, so it should preserve the identity of the function:
Copy code
fun <T, R> fix(f: ((T) -> R, T) -> R): (T) -> R {
    lateinit var fixed: (T) -> R 
    fixed = { f(fixed, it) }
    return fixed
}
e

ephemient

11/11/2023, 6:04 AM
avoid that altogether with
Copy code
fun <T, R> fix(f: ((T) -> R, T) -> R): (T) -> R = object : (T) -> R {
    override fun invoke(it: T): R = f(this, it)
}
or by just doing that directly instead of using a fixed-point combinator, we're not programming in lambda calculus