Proposal: externally-defined companion objects. A...
# language-proposals
e
Proposal: externally-defined companion objects. Allow defining companion objects externally, similar to what Scala does (https://docs.scala-lang.org/overviews/scala-book/companion-objects.html). Same motivation from my previous (https://kotlinlang.slack.com/archives/C0B9K7EP2/p1597588637286600) proposal: • Allowing to add extension functions for Java types (solves most-voted YouTrack issue: https://youtrack.jetbrains.com/issue/KT-11968). • Improving consistency with the way non-companion extension functions work, as they don't require special declarations on the class being extended. Proposed syntax:
Copy code
package com.fasterxml.jackson.module.kotlin

import com.fasterxml.jackson.databind.ObjectMapper

companion object ObjectMapper.Kotlin {
    fun forKotlin(): ObjectMapper = ...
}

// Would then also work
fun ObjectMapper.Kotlin.withAlphabeticallySortedProperties(): ObjectMapper = ...

// Usage from another file
import com.fasterxml.jackson.module.kotlin.ObjectMapper.Kotlin

fun main() = ObjectMapper.forKotlin()
This eliminates the need to add empty
companion object
declarations in classes solely for the purpose of allowing extension functions and allows defining extensions for Java types. The difference between internally and externally defined companion objects would be the ability to access private and protected members. For more information on use cases and problems of the current alternatives, see my previous proposal (linked above).
g
Same package of original ObjectMapper
I don’t think it will work with Java 9+, where you cannot have the same package in different modules, so it limited to a single module it would be almost useless
Also if it uses the same package (and we do not talk about java modules), how it would be possible to solve conflict when different modules/libraries add the multiple companion objects or even add the same extension
I’m a big supporter of KT-11968, but I think it requires solution which closer to what we have with extension functions, so one could import companion object for another class same way as import extension
e
Good point. My initial message contained an import of the original symbol instead of a package declaration. That would prevent the module issue you raised, but then, it would be necessary to import the companion object declaration. I don't see any problem in doing this, but I'm not sure about the impact of allowing multiple external companion object declarations
For example:
Copy code
// data/User.kt
package example.data

data class User(val name: String)

// serialization/User.kt
package example.serialization

import example.data.User

companion object User {
    fun fromJson(content: String) = ...
}

// database/User.kt
package example.database

import example.data.User

companion object User {
    fun fromResultRow(resultRow: ResultRow) = ...
}

// main.kt
package example

import example.data.User
import example.serialization.User.fromJson
import example.database.User.fromResultRow

fun main() = ...
I kind of like the explicitness of it, and it would be consistent with non-companion extension functions
In the end, external companion objects would pretty much act like namespaces
I updated the proposed syntax to reflect this. Thanks a lot, @gildor
g
but I’m not sure about the impact of allowing multiple external companion object declarations
It may work, same way as multiple extension functions imported for the same class, but if different companions have conflicting declarations, it make sense to allow rename companion, same as for extension function, so it will change syntax frrom ObjectMapper.forKotlin() to ObjectMapper.CustomName.forKotlin(), same as it works now with named companion object
again, just a rough idea
Also I’m not sure that defining companion object just by convention (use the same name as original class) is a good idea
Not sure how to solve it, but maybe it should be just a compiler trick when
fun ObjectMapper.Companion.withAlphabeticallySortedProperties
just works for objects even without companion object? it wouldn’t be possible to define real object, but at least it would allow to define extensions
e
Yeah, I'm thinking of that right now, because in my example, I define a
companion object ObjectMapper
and then return
ObjectMapper
in
forKotlin
, but these are actually different types.
I think if only companion object member imports are allowed, then it would not be an issue
But still there's the declaration conflict when a function returns the original type
Maybe it could be solved by declaring
Companion
explicitly:
Copy code
companion object ObjectMapper.Companion {
    fun forKotlin(): ObjectMapper = ...
}
This would resemble companion extension function definitions, since they also require referencing
Companion
And then imports would be
com.fasterxml.jackson.module.kotlin.ObjectMapper.Companion
This could actually be any name, just like internally-defined companion objects
g
But what about just:
Copy code
ObjectMapper.Companion.forKotlin(): ObjectMapper
e
You mean allowing
.Companion
extension functions even without a companion object declaration?
g
so what I’m saing, that now it required to have companion object, but maybe this what should be changed, actual companion object is not required, not sure how it would be represented in bytecode, probably by some name convention
yes
e
Yeah, my previous proposal goes along these lines, but I had a hard time finding a good syntax for it
I suggested the
fun ObjectMapper::forKotlin
syntax and automatically creating companion objects, but I think allowing to define external companion objects would be more intuitive
g
but it doesn’t require any additional syntax, isn’t it?
so there is no companion object on runtime, just a convention in source code to enable Componion object.-like syntax
didn’t think about it in this way, I also though about having multiple objects, but what I see now, it looks that actual object may be not needed
e
Yea, the problem is that companion objects can have a name, so automatically creating companion extension functions when they are defined as
.Companion
could be an issue
It would also suggest that every class has a companion, because companion objects today are actual objects with full capabilities
So you could do
fun ObjectMapper.Companion.forKotlin
but would not be able to do
println(ObjectMapper.Companion)
g
I think adding actual comopanion to every class is huge waste of resources and bytecode, essentially you automatically double amount of classes to generate
e
Yeah... of course the compiler could create companion objects only when there are extension functions defined for the given class, but the generated code would be external to it, and then would need an import
I also thought of a syntax like
companion fun Foo.bar()
and
companion val Foo.baz
but it would be confusing for Java types since there's no actual companion object involved
g
could create companion objects only when there are extension functions defined for the given class
It will not work for libraries, also it cannot work for already created java classes
so essentially companion object should be defined in some external class
or do not have companion object at all for extensioons
e
Yeah, I think it would be the only consistent way
I updated the proposal again according to what we discussed
Regarding conflicting declarations, I think it could be solved with "static" imports and aliases:
Copy code
import com.fasterxml.jackson.module.kotlin.ObjectMapper.Kotlin.forKotlin as forKotlinFoo
But I don't think factory methods, which are the main use case for this, are so common that conflicts would happen often
g
Yes, if it would be import of only particular extensions for companion (so no import of additional companion), then it would work as existing extensions, it wouldn’t require any additional syntax
👍 1
e
Hey @elizarov. Saw you voted for KT-11968. Do you have any feedback about my proposal idea?
e
The proposed idea cannot be made to work, but the problem itself is quite important and we are definitely keeping it on our radar.
👍 1
e
Right. Thank you!