Static members and type extensions #427
Replies: 21 comments 171 replies
-
Was there any consideration of using a class Direction {
static {
val UP: Direction
val DOWN: Direction
val entries: EnumEntries<Direction>
fun valueOf(value: String): Direction
fun values(): Array<Direction>
}
} |
Beta Was this translation helpful? Give feedback.
-
I think this example from §5.4 (type extensions, no mangling scheme) either requires backing fields in JVM or is missing
|
Beta Was this translation helpful? Give feedback.
-
I still feel like simply naming it class A {
namespace fun B::namespace.foo() {}
} we call
This could also nicely introduce standalone namespace feature for much more cooperative grouping work. namespace FlowCollection {
suspend fun <T> Flow<T>.collect(): Unit
suspend fun <T> Flow<T>.collectToList(): List<T>
} |
Beta Was this translation helpful? Give feedback.
-
I strongly dislike current proposal. It completely discards the discussion of previous iteration, which I find a bit insulting. The fundamental syntax is OK (though I liked scoped variant more), but naming is terrible. It does not reflect any mental model at all. In Kotlin we cand declare things in file, in class, or event inside function scope. We have a hierarchy of namespaces. We even use it broadly in teaching and in common phrases like namespace pollution. We have companion objects as singletons tied to a namespace bound to a class name. Extensions are added to class namespace. Companion extensions are extensions to companion object namespace. It is obvious what is the mental model for this thing. In the proposal not only, you introduce a completely new mental model, which you can't really explain without Java reference. Even more, you introduce TWO new models. Calling My proposal to fix things is following:
|
Beta Was this translation helpful? Give feedback.
-
My ideas from previous discussion are summarized here: #348 (comment) |
Beta Was this translation helpful? Give feedback.
-
I was about to comment the same thing as @SPC-code on how this new version ignores the discussion on the previous proposal, where we can see there's a consensus around I also strongly disagree with using the Lastly, I don't think there's any benefit in allowing For reference, this is the original request, which had most upvotes before it was renamed/reworked into this broader scope: |
Beta Was this translation helpful? Give feedback.
-
I strongly support @SPC-code about most of his points. However, I have to mention several minor issues in his argumentation.
|
Beta Was this translation helpful? Give feedback.
-
Let me add a different view point which I think is being somehow ignored in the discussion: we really really care about providing access to the "static" ideas in the underlying platforms. Java, JS, Swift, almost all the platforms we compile to or want to interoperate to have such a notion; and sometimes require using a static method in particular places. The current solution: defining something in a companion object and annotate it with On that topic, any proposal which does not directly map statics in Kotlin and statics in Java should come with a solution about how to reflect statics in Java coming from dependencies. In this KEEP we say "the current way is done in the compiler is OK", and simply extend this idea to defining static members. |
Beta Was this translation helpful? Give feedback.
-
Additional thing, I forgot to mention yesterday (actually it is @lounres, who mentioned it in the community discussion). |
Beta Was this translation helpful? Give feedback.
-
As far as I understand, one of the use-cases for namespaces is the ability to provide a partial qualified name. Currently, it can be done via objects, but they lead to storing everything in one file, which is bad. object A {
object B {
val a = 3
}
} import A.B
fun main() {
println(B.a)
} The reason to do so is escaping namespace pollution and not specifying potentially long full qualified name at the same time. This feature itself can be done without introducing new concepts and keywords at all. We can just allow importing packages which already serve as namespaces, so there would be no need to create yet another namespace analogue. Using Another thing that A solution here would be the introduction of package blocks. package a.b
package c {
package d {
val e = 3 // fully qualified name is a.b.c.d.e
}
} There even shorter form can be introduced. package a.b
package c.d {
val e = 3 // fully qualified name is a.b.c.d.e
} If you see any other use-cases for namespaces, @SPC-code and @lounres, tell me. |
Beta Was this translation helpful? Give feedback.
-
It feels like this is going to end up like the previous proposal. Basically, we seem to have two perspectives.
So how do we tackle this difference in ideas for the future of the language? In this discussion we have also seen counter-arguments to the user perspective.
Edit: and before someone comes up with the idea of using yet another annotation to change the behavior of the language, please don't. The amount of annotations is already ridiculous, let's not complicate it further. |
Beta Was this translation helpful? Give feedback.
-
As a user, how do I call a static function with a receiver? package my.package
object Greeter {
fun hello(name: String) { ... }
}
class ClassRoom {
static fun Greeter.helloFolks() = hello(name = "folks")
} If I am only allowed to call it from other static functions inside |
Beta Was this translation helpful? Give feedback.
-
Actually, you do not have to introduce anything static-like at all. There are two problems that this KEEP solves:
Point 2 could be achieved with Point 1... is it needed at all? The whole Ktor codebase has only 15 places where class MyClass
fun MyClass::namespace.main(){
//
} It actually looks much better than |
Beta Was this translation helpful? Give feedback.
-
Instead of answering different threads on small issues, I'd like to discuss the overall
One of the alternative designs we considered is having some sort of
At this point you don't really fix any of the problems there, you need to have some kind of "companion namespace" similar to companion objects. If we want to fix problem 2 (interoperability with Java) we need to "lift" the members of the companion namespace into static members. class Foo {
companion namespace {
fun bar(): Int = ...
}
}
// compiles to
class Foo {
static int bar() { ... }
} This also causes some problems in the model, because the same code is viewed in two different ways from the reflection perspective: within Kotlin you would like to say that the members are part of the companion namespace, but from the Java point of view they appear as static members of the underlying class. However, I think the main problem comes at a conceptual level: if you want to compile to static members in Java, that means that the members in the companion namespace cannot access instance members of the enclosing class. This means that There's also an additional problem of nesting here. People around here may like the nesting of Still we haven't talked at all about how we solve problem 1 (extend class scope). The key point here is that we need to assume that every class has some kind of companion namespace if we want to be able to extend it. It seems like the only possibility is to have something very similar to what the KEEP does, except that with a different reasoning to get there. fun Foo::namespace.f() = ... At this point, you may also ask why it's not simply better to just assume that every class has a companion object instead. After all, we could also have special syntax to mean "the companion object or a virtual one if it doesn't exist". fun Foo::companion.f() = ... In any case, it seems that for "extending static/class scope" the only available solution is to provide a direct way to do this. At this point the question is more about aligning the conceptual model than the syntax and feature that the users see. In summary, trying to design namespaces to fix these problems ultimately require some sort of companion namespaces, and additionally syntax to extend those. However, you end up shoehorning a particular compilation strategy if you care about Java interoperability. |
Beta Was this translation helpful? Give feedback.
-
Could the Motivation section be clarified?
This is already supported by extension functions.
This is already supported by top-level functions like
This I do understand is a problem currently.
This is already supported by using
This is the strangest goal for me. Instead of aligning enum entries to the rest of the language, this proposal is adding an entirely new concept to align enumeration entries, from the starting point that they are the odd ones out. Overall, the "companion object requires instantiating a long-lived object" problem is the only motivation I understand here, and it seems to be that there is probably a much simpler solution than introducing the entire concept of statics just for that. |
Beta Was this translation helpful? Give feedback.
-
data class Vector(val x: Double, val y: Double) {
static val Zero: Vector = Vector(0.0, 0.0)
}
val Vector::type.UnitX: Vector get() = Vector(1.0, 0.0)
val Vector::type.UnitY: Vector get() = Vector(0.0, 1.0) I don't see this point being mentioned, for ease-of-learning, this two should definitely be named the same. If the keyword stays |
Beta Was this translation helpful? Give feedback.
-
I'm in favor of forbidding static fake constructors even more so than regular operators. Regular operators are problematic in static context because they often imply state. However, that is even more so the case with Fake constructors using And yet, If there is a need to have fake constructors at the language level, something like |
Beta Was this translation helpful? Give feedback.
-
After some more direct discussion, it seems to me that there are (at least) two camps on the whole "namespace" idea. This gives us three broad options: 0️⃣ Keep the same KEEP
1️⃣ Keep almost the same KEEP, but rename "static" with "namespace"
2️⃣ Introduce a notion of top-level namespace, and use "companion namespace" as the way to attach members at the class level (which I described here) Just to be clear, could people here emoji-respond in which camp they are? Or if their position is not being (roughly) represented here, how does it differ with all three of them? |
Beta Was this translation helpful? Give feedback.
-
I see many opinions which point only at one side of the situation. So, I decided to make an overview of it. Status Quo
ApplicationsTotally there are 1.8M usages of companion objects. By categories:
96% of users use
ProblemsDespite the huge functionality the feature gives, it causes several problems. Scope breakageThe concept of scope is widely used among programming languages and Kotlin especially. class A {
private val a = 0
// a is available here
// b, c and d are not avalable here
fun g() {
val b = 1
// a and b are available here
// c and d are not avalable here
fun f(c: Int) {
// a, b and c are available here
// d is not avalable here
if (c == 0) {
val d = 3
// All a, b, c and d are available here
}
// a, b and c are available here
// d is not avalable here
run {
val d = 3
// All a, b, c and d are available here
}
// a, b and c are available here
// d is not avalable here
}
}
}
class A {
private val a = 0
// All a, b and c are available here
companion object {
private val b = 1
val c = 2
// b and c are available here
// a is also available but only on instances of `A`.
}
} One may say that class A {
private val a = 0
// a is available here
// b and c are not avalable here
// ↑
object Nested {
private val b = 1
val c = 2
// b and c are available here
// a is also available but only on instances of `A`
}
} With modifier protected, open class A {
companion object {
@JvmStatic
protected fun f() = Unit
}
}
class B: A() {
fun f() = A.f()
} Here On the other hand some Inconvenient syntaxIt is quite often that you want to place some instance-less member near its only usages to improve readability and reduce navigation among the codebase during reading. However, it is not possible if the Furthermore, creating the Java interopabilityBy default
instead of
Reflection breakage
class Outer {
companion object {
@JvmStatic fun f() {}
}
}
fun main() {
println(Outer::class.java.methods.map { it.name })
}
Non-extensibility of Java classesJava classes have no companion, so they are not extensible. Slower class initializationAs companion object is yet another class, it requires additional initialization which affects large codebases if the member is not Approaches to solve and their drawbacksCombining with namespaces feature.Code example: class Outer {
fun f() {}
// member of namespace Outer being also a class
namespace fun g() {}
}
// standalone namespace
namespace Std {
fun f() {}
}
// namespace extension
fun Outer::namespace.h() = g() Drawbacks
DisclaimerIt does not mean that namespaces shouldn't be implemented but they should be discussed and implemented separately in a different KEEP. Adding fake companion to Java classesIt would solve the problem of extensibility. Drawbacks
Ability to specify
|
Beta Was this translation helpful? Give feedback.
-
Summarizing a bit what we seem to agree on this discussion up to now (18 June 2025):
|
Beta Was this translation helpful? Give feedback.
-
This argument applies to any Example when a class defines a class Foo {
fun type() {}
}
fun Foo::type.zero() { // type extension over 'Foo'
val k = Foo::type // k has type 'KFunction1<Foo, Unit>'
} Example when a class defines a class Bar(val type: String)
fun Bar::type.zero() { // type extension over 'Bar'
val k = Bar::type // k has type 'KProperty<String>'
} You can exchange Actually, I would argue that using Instead of arguing about the suffix name, it may be better to use a different type/class extension function syntax anyway. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
This is an issue to discuss static members and type extensions. The current full text of the proposal can be found here.
We propose to bring the idea of statics in Kotlin with static members and type extensions.
The underlying idea is surfacing the notion of static scope more clearly in the language.
ℹ️ This is our first attempt to use GitHub discussions for KEEP review. Please try to reply on the appropriate thread, and to create separate messages for different feedback or concerns, so we can keep everything tidy.
Beta Was this translation helpful? Give feedback.
All reactions