As you all know, functional interfaces were added ...
# announcements
p
As you all know, functional interfaces were added in Kotlin 1.4. Tbh, for me, a backend guy, this feature was something that I didn't hear before. What are some real world usecases for it? Thanks!
t
I can think of mostly two, none of which is really technical, but have to do with code maintainability (you know the drill, write it once, read it a billion times): • less code: functional interfaces are defined as interface with a single method, the language can provide you with shortcuts on how to write implementation. for instance look at the difference with and without SAM: https://kotlinlang.org/docs/reference/fun-interfaces.html#functional-sam-interfaces • single responsibility principle: the less a class is doing, the easier it is for our brain to parse it. again, by providing a concise syntax that favours this good practice, kotlin is encouraging developer to write more maintainable code
👍 2
c
I’ve written libraries that depended heavily on fairly complex callbacks (2-3 parameters with return types and generics). Until 1.4, the methods accepting those callbacks would have had to either: 1. define every parameter going in/out of each lambda type (making the library code ugly) 2. I would make an interface and callers would have had to use an anonymous object to call it (making their code ugly) 3. Use a typealias to define the type of that lambda, which just felt dirty to me, but is ultimately what I would do With
fun interface
, I don’t have to compromise on type-safety or legibility. I write the library using a normal interface for safety and clean library code, and consumers can just use SAM lambdas instead of anonymous objects
p
Great, thanks for the insight!
n
@Casey Brooks I guess I don't really follow, what's the downside of option 3? It seems like it's not really less legible or type safe.
i
Option 3 doesn't work if you want compatibility with Java callers as well
c
Take the following example:
Copy code
kotlin
typealias MyCallback<T> = (String, Int, T) -> T

fun <T> runStuff1(t: T, block: MyCallback<T>) : T {
    val s = "s1"
    val i = 1
    return block(s, i, t)
}

fun <T> runStuff2(t: T, block: (String, Int, T) -> T) : T {
    val s = "s2"
    val i = 2
    return block(s, i, t)
}
As far as the code itself goes, it’s perfectly type-safe. But there’s no way to enforce the correct usage of the
MyCallback
typealias, so you might run into a situation where some methods are using the typealias version of the parameter and some are using the lambda syntax. It’s perfectly legitimate code, but the “type” is not safely used throughout. Further, if changes were made to the
MyCallback
interface, those places using the lambda syntax would not be updated to reflect those changes.
n
@Ian Lake Sure, that part I agree with
@Casey Brooks I don't really follow. Correct usage, by whom? The person writing the library, or the client?
I think I understand. You are saying that runStuff1 and runStuff2 are not forced to be consistent. That's true, but how is this different if we had an interface instead? You could still end up using an interface in some places, and the "lambda syntax" (really a function type parameter) in others, and they would both silently work as long as lambdas were passed.
c
Both. The library author could write functions using either lambda (
(String, Int, T) -> T
) or typealias (
MyCallback<T>
) syntax, and both are valid as far as usage of the function is concerned. But the intention in setting up the typealias is that it would be used everywhere, and so writing functions with a parameter defined using the lambda syntax breaks that intention, but because it’s a typealias the code will still compile. And then if you were to use IDE refactoring tools to change the definition of the typealias, the functions using the lambda syntax will not be updated and your library code is now broken. And a similar thing happens on the consumer side if they are passing that lambda parameter around themselves. If you used a
fun interface
, you could not write function parameters with lambda syntax. They all would be the same type, and that gets passed around, called, and refactored safely everywhere. The consumer of the library is forced to use SAM lambdas (acknowledging the strong-type of the interface), and anywhere they choose to pass that lambda around is a strongly-typed object. The “safety” i’m referring to is more more about the declarations of functions and the types of its parameters than the actual call-site.
n
Why couldn't you write function parameters with lambda syntax, if you used a fun interface?
I just don't understand what's preventing that
just change your example above so that
MyCallback
is an fun interface
c
If you write
MyCallback
as a
fun interface
, it can no longer be assigned to
(String, Int, T) -> T
, and it can’t be passed into other methods with
(String, Int, T) -> T
as a parameter. If it was a typealias, you can freely use change between one and the other. Maybe this example will help (nevermind the infinite recursion, just notice how it fails to compile. Also, try removing
fun
from the interface and see how things change) https://pl.kotl.in/fulUE6Ngr
n
Okay, so the difference is that you only have implicit conversions in one direction
Lambdas can become fun interfaces but not vv
Which is different from type aliases
That can help with consistency, though it doesn't guarantee it, just catches certain kinds of inconsistencies but not others