Can someone explain this to me? I understand exten...
# getting-started
d
Can someone explain this to me? I understand extension functions, I understand generics. I don't understand how you can have an extension function that works like this:
Copy code
fun <M: UiModifier, T: Any> M.installDragAndDropHandler(
    dndContext: DragAndDropContext<T>,
    handler: DragAndDropHandler<T>,
    dragItem: T
): M {
    onDragStart { dndContext.startDrag(dragItem, it, handler) }
    onDrag { dndContext.drag(it) }
    onDragEnd { dndContext.endDrag(it) }
    return this
}
Does this add the extension function to all UiModifiers?
j
To all `M`s inheriting from
UiModifier
?
d
Yes. That's probably a better way to phrase that.
m
The main reason why generics is used here is to keep the return type the same as the input so if you have a simple ext function the return type is going to be
UiModifier
all the time but now with generics let's say that you have
PrimaryModifier
a child of
UiModifier
, using this ext function on
PrimaryModifier
will return a
PrimaryModifier
not a
UiModifier
, that's the main advantage of using generics here.
d
I get how it can be useful, I just don't understand what it does. Will all classes that inherit from UiModifier have this extension function?
e
I would maybe write it as
Copy code
fun ...(...) = apply {
}
to make it more immediately clear that it always returns
this
even if it were written as
Copy code
fun <T: Any> UiModifier.installDragAndDropHandler(
    dndContext: DragAndDropContext<T>,
    handler: DragAndDropHandler<T>,
    dragItem: T
): UiModifier
it would be applicable to all subtypes of
UiModifier
d
The part that is weird is that the generics apply to the very classes the generic is being applied to.
e
the difference with
<M>
is that the return type stays narrow
m
Yes exactly, in term of visibility, it's the same in both cases. It's going to be visible for all classes that inherit from
UiModifier
e
e.g.
Copy code
val m1: SomeUiModifierSubtype
val m2 = m1.installDragAndDropHandler(...)
without
<M>
,
m2: UiModifier
, so you might need to cast or reorder if you want to call
SomeUiModifierSubtype
-specific methods on it
d
I guess the compiler at compile time knows all the classes that inherit from UiModifier and so knows to the apply the extension function to all of those classes. Seems pretty powerful.
Although why wouldn't you just include it in the base class itself as opposed to doing it this way?
Just because of the return type?
m
The return type is a big advantage in most cases, as @ephemient mentions, you can access child specific methods and to avoid casting.
d
And if you included the method in the base class you'd have to manually cast it in the children?
m
exactly
e
self-type is not available on JVM nor in Kotlin's type system
d
For example if you had:
Copy code
open class UiModifier() {
  fun <T: Any> M.installDragAndDropHandler(
    dndContext: DragAndDropContext<T>,
    handler: DragAndDropHandler<T>,
    dragItem: T
  ): UiModifier {
    onDragStart { dndContext.startDrag(dragItem, it, handler) }
    onDrag { dndContext.drag(it) }
    onDragEnd { dndContext.endDrag(it) }
    return this
  }
}
Then in the child class you'd have to override it, call super and then cast it.
e
and you'd have no way to enforce that all child implementations do that
m
Copy code
val modifier: CustomModifier = ...
val newModifier = modifier.installDragAndDropHandler() // this will return UiModifier
e
right, unless overwritten to be more specific in
CustomModifier
m
Yes, but if you are going to override it, first you need to do it for every child also it's a lot of boilerplate code
d
Got it.
Very interesting combination of extensions and generics. Do other languages do this? I haven't run into it elsewhere.
m
AFAIK this is not possible using Swift only using a normal function not extension, idk for other languages but yes Kotlin contains a lot of cool features
K 2
🙌 1
k
By the way, note that extension functions are not some kind of magic, they don't really extend the class itself, despite their name. (Or shall I say, they're not a proper part of the class they "extend".) They're just syntactic sugar. For example, extension functions are not overridable. Now, if it were an ordinary function:
Copy code
fun <M: UiModifier, T: Any> installDragAndDropHandler(
    uiModifier: M,
    dndContext: DragAndDropContext<T>,
    handler: DragAndDropHandler<T>,
    dragItem: T
): M {
    uiModifier.onDragStart { dndContext.startDrag(dragItem, it, handler) }
    uiModifier.onDrag { dndContext.drag(it) }
    uiModifier.onDragEnd { dndContext.endDrag(it) }
    return uiModifier
}
Would you now find it easier to understand? Well, just because an extension function's syntax looks as if it's part of the class, it doesn't mean it is a part of the class.
💯 1
d
Interesting. But would they have access to internal and private members of the class or no? Or are they truly indistinguishable from an external function with a dependency injection?
e
they have the same access as an external function because they are external functions.
internal
works in the same module,
protected
and
private
are inaccessible
👍 1