Hi, I have a question, why is the Kotlin SDK defin...
# getting-started
a
Hi, I have a question, why is the Kotlin SDK defining extensions methods of classes that haven't any parent or not implementing anything ? Is that better than defining the method inside the class ? What are the benefits so ?
r
Could you mention some specific examples?
d
What SDK?
You mean the standard library?
a
Yes
s
Consider the case of a String class. Here you can’t your own method inside String class. But kotlin extension method, you can do it
a
Yes but for personal classes, what is the case ?
Thanks @Ruckus I will read that !
👍 1
r
For me it has four important use cases: 1) Adding methods to types you don’t control. Many Kotlin stdlib extension methods decorate java stdlib types. 2) Adding methods that it would be inappropriate to add to the original type, perhaps because they are far too specific to a particular context - otherwise a type could end up having thousands of methods. You can group extension functions for a particular context in a file to reduce the cognitive load of understanding the “basic” class. 3) Adding methods that cross domains Perhaps you want to have this:
Copy code
val rex: Dog = TODO()
val bmw: Car = TODO()

rex.getInto(bmw)
Obviously the domain of
Dog
shouldn’t know about the domain of
Car
, and vice versa. But it’s quite nice to call it that way, rather than
putDogIntoCar(rex, bmw)
or something. 4) Adding methods which would otherwise introduce circular dependencies. For instance
Any
having a method
toString()
while
String : Any
introduces a circular dependency between
Any
and
String
. It’s there because Java defined it that way on
java.lang.Object
, but it would be much better if there were an extension method
Any.toString()
- then there’s no circular dependency. Of course you could just have plain old functions, but extension functions: 1) Allow you to maintain “subject verb object” ordering 2) Allow using null dereferencing -
fun Foo.bar()
can be called
foo?.bar()
if
foo
is nullable, which is much nicer than
foo?.let { bar(it) }
3) Allow discovery in an IDE - it’s nice to be able to go
foo.
, hit control space and see the extension methods as possible things you want to do.
It is a circular dependency. You cannot compile Any without compiling String, and you cannot compile String without compiling Any. It’s unquestionably a circular dependency.
You can’t refer to a type without the compiler knowing it exists. So you cannot compile either in isolation.
a
Thanks a lot all, it's more clear for me now !
r
I don’t think we’re going to reach agreement on the circular dependency question. As far as I can see them being in the same module doesn’t stop it being a circular dependency between the classes, it just makes it a circular dependency that the compiler & runtime can cope with. You’re quite right & I was wrong about the extension method - of course it wouldn’t be a get out of jail card to allow
toString()
on Any - the static compilation means the polymorphic version would be ignored.
(In terms of the polymorphism, I think you should need to implement an interface
interface Show { fun toString(): String }
. String interpolation should require an explicit call to
toString
, too - so often you see untested code, particularly logging, where an object which hasn’t got a useful toString is interpolated. I’m with Haskell on this. I also like the way that implementing an interface happens after the declaration of the type in Haskell, so you can effectively make
Any
implement
Show
after the declarations of
Any
and
Show
, avoiding the circularity.)
I think 4) still holds as a reason for extension methods, but Any was a poor example. And 4) is probably therefore a specific example of 3), in the case where the domains are in different modules, and adding the method as a member of a class in one module would introduce a circular dependency.
a
No problem it's interesting !
👍 1
j
@Ayfri To add to the use case Rob mentioned regarding your question
Yes but for personal classes, what is the case ?
As Rob mentioned, sometimes we want to extend functionality that is only relevant to part of the code, and not the class itself. And using extension functions has the advantage over utility functions in that calling a function on the class as a receiver can feel more “natural” in its use than passing it into a function. Let’s say in one part of my code I need to validate a person. Validation is specific to the place the Person object is being used in. For example here validation is they are 21 or older. In another part of the code a “valid” person might be 16 and older and have a driver’s license. So I would not add the function to the Person class, but to where I need it. I can write either:
Copy code
private fun validate(person: Person): Boolean = person.age >= 21
private fun Person.isValid(): Boolean = this.age >= 21
When I use those, the extension function just presents itself better in the code. It reads more naturally.
Copy code
if (validate(person)) { ... }
if (person.isValid()) { ... }
Also, with extension functions, I can create a fluent API to use rather than calling a series of utility functions:
Copy code
internal fun Document.normalize(): Document
{
    // normalization code
    return this
}

internal fun Document.convert(): Document
{
    // conversion code
    return this
}

internal fun Document.process(): Result
{
    // process code
    return Result()
}
And when I use it, it results in very readable succinct code:
Copy code
val document = ...
val result = document.normalize().convert().process()
If I had to do that with utility functions, I’d have something like:
Copy code
var document = ...
document = normalize(document)
document = convert(document)
val result = process(document)
Not only is that more lines of code, it doesn’t read nearly as well. As mentioned in the book Kotlin in Action, a leading design goal of Kotlin is to make it as readable as possible. That’s because as developers, we actually spend more time reading code, then writing it. Moreover, in the first use with the extension functions, my
document
is a
val
. But when I use the utility functions, I had to make it a
var
We want to favor immutables as they result in less bugs, and are better for functional programming.
a
Thanks you for your response I understand when to use them and why!