Comments (122)
I agree with the suggestion to replace static object
with namespace
. IMO a lot of people read 'singleton' when they see object
, and 'static singleton' doesn't make a lot of sense. As you said yourself:
Static modifier is not allowed on other declarations (including class, interface, and typealias declarations). Rationale: static modifier on a class, interface declarations make no sense, as such declarations are already static by default and do not have access to an outer class instance.
The exact same rationale is also applicable to object
s.
from keep.
Great proposal!
Few concerns that popup in my head.
Will companion object
s be deprecated? I hope not, since these can implement an interface. Or will static interfaces allow for a similar way of having a static 'reference' (in code that uses it) that satisfies that interface?
I'm not a fan of static object
s being effectively a namespace (no this
reference). The word object
implies there is a this
reference (to that object). I would favor a new keyword, eg namespace
.
from keep.
(on behalf of @altavir)
Looks nice. I definitely like blocks more than modifier. It follows the overall tendency of using scopes to designate changes of code block semantics. It also encourages to group static statements together instead of placing them in random places in the class.
Also it would be nice to consider using namespace
used in early design instead of static
. I understand that static
is familiar from other languages. But namespace
better corresponds to the role the feature plays in Kotlin. In Java, where everything is a class and must be dynamically instantialized, it makes sense. But in Kotlin we have top level functions, objects etc. The concept of static
does not make a lot of sense. On the other hand namespace
makes a lot of sense. It emphasizes the fact that we have a concept of a named scopes (packages, objects, classes, etc) and we can create namespace hierarchies.
The question of interoperability with JS is also interesting in the context of https://youtrack.jetbrains.com/issue/KT-46164
from keep.
The concept of "static sections" could be generalized to "modifier sections" with equivalent private and internal sections. Each modifier section would then apply its modifier implicitly to all contained members.
In cases where it would favor readability, these sections could be collapsed to regular modifiers as used presently.
While the proposal explicitly rejects such a hybrid approach for static, the more general case may be worth considering nonetheless.
from keep.
I even would go further and suggest allowing namespace
modifier on ext functions, to replace fun Color.static.myExtension()
with namespace fun Color.myExtension()
. IMO, the latter nicely reads as 'function in the namespace of Color'. Color.static
, on the other hand, introduces some specific entity that is a special case — normally, we are allowed to write fun A.B.x()
only if B is a class or object inside A. Avoiding special cases and providing concise, but explicit syntax is one of the Kotlin's design cornerstones.
from keep.
Alternative proposals:
- instead of
static
section havestatic companion object
, so that it is symmetric withstatic object
, or - (IMHO better) as mentioned above, replace
static object
withnamespace
, thus also havingcompanion namespace
(instead ofstatic
section).
Pros of 1):
- Symmetry: you can mark both
object
s andcompanion object
s static, both having the same semantics (and also the same as in KEEP). - Symmetry in extensions: both static and regular
companion object
s can be extended with the same syntax (fun MyClass.Companion.foo()
. KT-11968 is solved by all classes implicitly having an emptystatic companion object
. It can be then explicitly replaced with user-specified one, eitherstatic
or not. - Less new concepts in language (
static
keyword only on objects, no newstatic
blocks).
Cons of 1):
- You (probably) cannot have both
static
and not-static
object in a class. - You (probably) cannot have multiple
static
companion objects like you can withstatic
sections. static companion object
is rather lengthy.static companion object
looks kind of likestatic class
es in Java - confusing and boilerplatey
But those cons are solved in option 2):
- There can be both
companion object
andcompanion namespace
in a class. - You (probably) can have multiple multiple
companion namespace
s in a class - The symmetry is still achieved with
namespace
andcompanion namespace
- You can have both named and companion
namespace
in a class, the same as you can now have both named and companion objects. - Like in 1), KT-11968 is solved by all classes implicitly having an empty
companion namespace
which can be extended. - Extensions are (probably) like in the KEEP -
fun MyClass.namespace.foo()
.namespace
in this context also looks slightly more meaningful thanstatic
,
from keep.
Great write-up, but there doesn't seem to be any information about the ABI of platforms other than the JVM. What's the ABI of JS, and is it blocked/require ES2015 output from the compiler? Does the Objective-C interop use class methods or something else?
Also curious if external static function declarations work on JVM and JS?
from keep.
There is a class
, there is a class with 1 instance: object
, I'd expected a separate keyword for class with 0 instances. I like namespace
the most, since it describes exactly what static object
is supposed to represent.
from keep.
I'm worried this proposal is making the language much more complicated than warranted by its benefits. It seems to be that this is trying to find a unique solution for problems which would be better dealt separately, or they risk confusing the mental models we all have about the concepts of the language.
There is a major shortcoming to [reworking companion objects instead of introducing statics]. In the end, when the improvement process is over, we'll have a feature called a "companion object" in Kotlin that will work and behave very much like statics in all the other major and popular programming languages (see Statics in other languages), but under a different name, without offering any tangible benefits in conciseness or in reducing error-proneness of the resulting code. Basically, it is going to be "inventing a new name just for the sake of inventing a new name".
To me, this is a very strong point against the introduction of statics. Indeed, we already have an almost identical language feature, and adding a second one to Kotlin will be "inventing a new name just for the sake of inventing a new name". If companion objects were another KEEP, I would have agreed with this argument, but they are now well established in the language, and I don't interpret this proposal as wanting to remove them (though it mentions it is one of the long-term goals).
This is even more the case, because static
is a concept that means completely different things in various languages. Most notably, this proposal would use the Java meaning, which is completely different from the C meaning (which is about memory storage, not about namespacing or class members). The proposal itself notes how it differs between languages (e.g. whether static members are overridable). Specifically in this case, I think the familiarity argument is weak. This proposal also plans to add specific features in the future (e.g. static inheritance) that are not commonly associated with the static
keyword in all languages, further creating confusion as to the specific variant of static
used.
Statics in Java are complicated to understand for students, because they require a deep understanding of the differences between class and instances, and the fact that the static part of a class is neither, yet somehow behaves as an instance sometimes (static initializers…). Kotlin's companion object made the mental model much easier, because it is possible to teach objects and classes independently, after which a companion object "is just an object that shares the same name as a class"—their entire existence, with all its complexity, is explained accurately in a single sentence. Introducing statics now would bring back all their complexity, with the addition of having to explain to newcomers what the difference between companion objects is.
Extensions without a companion object
The main issue adressed by this proposal is the companion object's usage for namespacing, instead of as a proper object. This proposal implies that statics are the only solution to this problem, but this problem is stricly with syntax and doesn't require any change to bytecode or anything else. This seems to go against the philosophy of previous language features, which strive for compilation-details impacting a single platform to be modeled as an annotation instead of an entire language feature (e.g. @JvmName
, @JvmStatic
, the rename from inline class
to @JvmInline
, etc).
In Kotlin, class modifiers always add precision to their qualified keyword. A sealed interface
is an interface
. A value class
, a data class
or an enum class
are all classes. However, a static object
is not an object: it cannot participate in inheritance relationships, is not a type a variable can have, is not a value, doesn't have this
, etc. If I was asked to explain what a static object
would be before reading this proposal, knowing what object
meant in Kotlin and what static
meant in Java, I would have imagined some kind of proper object of which all members are used as if static: this is what Kotlin calls a companion object
, which is completely different, and already a language feature.
Furthermore, what is the difference (assuming the syntax of this proposal) between these snippets?
// Variant 1: static object
static object Constants {
const val PI = 3.14
}
// Variant 2: object with a static section
object Constants {
static {
const val PI = 3.14
}
}
My understanding is the second one actually creates a Constants
object
, whereas the first one doesn't. This is impossible to guess from just existing Kotlin knowledge.
Instead of introducing an entirely new concept which almost entirely overlaps with companion objects, I believe it would be better to profit from companion object's syntax: they are called companion object
, because they are objects that act as companions. The core of this proposal is wanting companions that are not objects.
class Foo {
companion object {
// it's a proper object, it's in the name!
}
companion {
// it's a companion, but not an object, it's in the name!
// it can't inherit, it can't have 'this', etc,
// that's obvious since these are features of objects,
// and this is not an object
}
}
// It's an extension on the object (objects start with an uppercase letter per the Kotlin code style; highlighted as an object)
fun Foo.Companion.bar() = …
// It's an extension on the "not object" (highlighted as a keyword)
fun Foo.companion.bar() = …
// Alternative syntax, inspired by context receivers
// This frees up the single receiver, which isn't used since it's not an object anyway
companion(Foo)
fun bar() = …
This also solves the "the simplest code should be the correct default". We want to encourage users to use companion
sections which are not objects by default, while still communicating that companion object
allows more features when they are useful. This seems to follow the teaching objectives of this proposal better than the proposed syntax.
I'm aware this is almost identical to the proposal. However, there is a major difference: it does not introduce new concepts to the language. Instead, it simply creates a companion
section that is not an object, which already implies all the restrictions on inheritance, the absence of this
… that this proposal brings, without requiring prior knowledge of Java or any other language. I think this is extremly important to avoid the confusion between "when to use static and when to use companion objects": the answer is simple, use companion object
when you need a feature of object
, use bare companion
when you don't. All restrictions can be deduced from the syntax alone.
from keep.
Great proposal.
The idea of statically implementing an interface is interesting, but I wonder if static interface
is the right concept. It implies that the interface "knows" that it will be (must be) implemented statically. An alternative would be:
interface Parseable<T> {
fun parse(s: String): T
}
class Color(val rgb: Int) : static Parseable<Color> {
static {
/*override*/ fun parse(s: String): Color { /* impl */ }
}
}
from keep.
@elizarov I read the explanation. The logic is sound, yet I can't help feeling dissatisfied with the results. I think the reason it's controversial is that if a beginner asked "what is the difference between static object
and just object
", you'd have to answer something like "there's no important difference but static object
is more efficient". That answer will prompt puzzled looks of the form "then why isn't it always that way" and you have to get into a discussion about backwards compatibility, the difficulty of proving nobody is relying on the edge cases etc.
Also seems likely that style guides will split on the issue - it's so cheap and quick to add one keyword to reduce bytecode bloat and improve performance a bit, that some people will say you should always write static object
. Others will say that no, the performance impact is hardly measurable and it's simpler/more consistent with old code to just use object
. Others will follow whatever the stdlib does.
This is one of those times that I wish Kotlin had a targetVersion
concept. Really, the default should be static object
and then if users need it to have an identity, you'd opt in to that. Without any notion of a defaults version though, our beautiful Kotlin is bound to accumulate such warts and wrinkles as it ages. It may be inevitable, logical and the best possible option at the time, but it still feels a little sad. Well, so be it.
from keep.
I agree with others about the static
modifier: if I want to create a namespace, then I need to define a static object
(a class and its instance without the instance).
We can define a static object
as a top-level object, but it isn't possible to define a companion static object
, further this KEEP allows us to define scattered static
sections in a class to add methods to the class's namespace.
In the section Static object primer
The goal of this object is to ensure that call-sites for the declarations inside it, like notNull(), are prefixed with a namespace Delegates and look like Delegates.notNull()
Multiple times in the KEEP the "namespace" word has been used to explain the goal of a static object
, in the last part of the document was explained that other languages use the static
keyword, but C++, Java, C3, JS/TS, Python, Swift, and Rust don't use static
to define packages, namespaces or, more in general, a group of declarations.
Instead, C# uses namespace
to define a group of declarations, possibly nested, and this is omitted.
From my point of view, namespace
is the name of this feature.
Inside a class, instead of a static
section, we should define at most one companion namespace
and possibly other namespaces:
class HttpClient {
companion namespace {
const val HTTP
}
namespace Responses {
fun string()
}
}
from keep.
Overall I love this proposal but I echo the sentiments of using namespace
over static object
as there is no "object" to speak of.
Also seems to be an unpopular opinion but I favour the modifier syntax over the sections. This is just my personal opinion but I feel the verbosity of a whole separate section makes this feel like a shorthand for just declaring a companion object. On that note, I understand the syntax for static extensions makes a ton of sense when you use sections, but from the modifiers perspective would something like static fun SomeClass.someMethod()
make more sense?
from keep.
For static inheritance, it would also be nice to be able to mix static and non-static abstract methods, e.g.
interface Serializable<T> {
abstract static fun deserialize(s: String): T
fun serialize(): String
}
You'd need some way to specify that the static method in the interface is abstract, which is what I used abstract static
for in the example. This is achievable anyways by having an interface implement a static interface, but IMO being able to mix them is quite a bit nicer.
from keep.
Yup, 'static' is both ubiquitous and ambiguous. Even as a keyword: static methods in Java, static functions in C and static local functions in C# are three different things. Or static classes in Java and static classes in C#. And then there are terms like static dispatch, static section...
Also most of the usages of static
as a keyword look like hacks. Like in Java: "methods are invoked on objects, but sometimes there is no object so we add static
". Not to mention static classes. The same applies to the whole C family (though C's static local variables actually look reasonable). They are like "its mostly this way, but with static its not".
I'd like kotlin to avoid this path. I do like companion object approach - they are just objects on their own - it's only that they are inefficient.
from keep.
I also like MyClass.namespace.something()
much better than MyClass.static.something()
.
from keep.
Thank you for the proposal. My comment is related to Static section vs static modifier.
I like the idea of static
section. I want to try to flip your view point a bit:
Assume static
is a scope without this
with it's own name:
top level declaration:
static Namespace {
}
instead of object (since object has this
in it's scope)
static object Namespace {
}
class declaration stays the same with an option to add named static groups:
data class Color(val rgb: Int) {
static {
fun fromRGB(r: Int, g: Int, b: Int): Color { /* impl */ }
}
static Predefined {
val RED = Color(0xff0000)
}
}
// usage
Color.fromRGB(0xff0000) == Color.Predefined.RED
extension:
fun Color.static.fromHSV(val h: Int, val s Int, val v: int) Color: { /* impl */}
val Color.Predefined.GREEN = Color(0x00ff00)
from keep.
There is a class, there is a class with 1 instance: object, I'd expected a separate keyword for class with 0 instances. I like namespace the most, since it describes exactly what static object is supposed to represent.
It's indeed confusing to have static
as a modifier of object
because static
does not modify a property of an object. Instead, it removes its essential property, which is having a single instance.
I agree namespace
would be better since it makes it clear that's a different concept.
from keep.
Great proposal. My comment is about Static section vs static modifier.
Benefits of static modifier syntax:
- It makes statics in Kotlin work very much [statics in other languages](https://github.com/Kotlin/KEEP/blob/statics/proposals/statics.md#statics-in-other-languages).
In my opinion, - if and only if there is not an indisputable design advantage for Kotlin - we should favour being symmetric with the languages Kotlin Multiplatform interop with: Java, Swift and JavaScript. Kotlin developers will often be "switching" between these languages and Kotlin, and introducing a new construct will create additional cognitive load during "language switching".
AFAIK (ignoring dispatching and technical details, focusing on usage), the 3 languages use static
as a modifier for variables, methods and/or properties (i.e., static fun staticMethod(): String
), and Java and JS uses static blocks for static initialisation (i.e., static { }
).
The proposed static
block is non symmetrical with these languages, and also conflicts with their usage of static
blocks (for static initialization).
For namespace for grouping members: they can rely on static object
(including for an easier migration).
I do understand Kotlin is a unique language and should not be bound to the other languages it supports, but it seems to me there isn't any indisputable design advantage in favour of static
blocks, but there is symmetry in favour of static
modifiers, and all use cases from the static
blocks can be covered by static object
.
from keep.
Thank you for your reply, @elizarov.
Sorry for the long post.
I read my previous post again, I think that the core question "static
as common or individual modifier" is misleading.
The question behind this KEEP is:
- Should Kotlin permit the definition of
static
members?
or
- Should Kotlin allow to extend a type namespace?
Each solution solves the other but leads to different consequences.
I think that Kotlin should allow extending namespaces, so my previous post.
Should Kotlin permit the definition of static
members?
The section Statics in other languages refers to Java as a good use case to approve static
in Kotlin.
In Java is possible to define instance methods or class methods.
Class methods are always invoked by invokestatic
and the target method has been defined at compilation time, so it is static and cannot change at runtime.
In Kotlin is already possible to define a static method, so adding the static
modifier to define static methods looks confusing to me.
We have to teach developers that, like Java, the static
modifier allows them to define static
methods, but at the same time, many other methods without the static
modifier are invoked statically.
What should be the reason behind the static
modifier? Should it mimic the Java behavior that has been designed for a different purpose?
Is the dispatching type relevant for this KEEP? I think not.
I think that the primary goal is: "Research and prototype namespace-based solution for statics and static extensions" (KT-11968)
Should Kotlin allow to extend a type namespace?
This KEEP explains why using package
may be lead to some disadvantages, and I agree with it.
However, it explains some other languages that use the static
identifier, but omits consideration about the usage of the namespace
identifier.
C# and C++, cited in the KEEP, already use namespace
, this KEEP uses "namespace" to explain motivations and the goal of itself.
I think namespace
is a good candidate to consider, considerably better than static
.
E.g: namespace object
, companion namespace
, namespace fun
, namespace const val
...
KEEP review
The feature that every Kotlin developer will use are static members, some will also use static extensions
fun MyType.foo()
is a definition of a static method on the instance MyType
, so what is the meaning of the "static members" or static extension here? foo
is statically dispatched.
So, a good design has to get the most important, the most actively used part of the proposal right — static members
The main goal of this KEEP is the ability to define List.of()
or Intent.SCHEME_SMS
without changing the original class, IMHO.
Are they static? Yes, every method's extension is already static in Kotlin.
Are they class members or namespace members? Yes, this is the missing part.
However, it would be inappropriate to call them "namespace members" in Kotlin.
As written before, it would be inappropriate to call them static
, IMHO.
It is not in Kotlin design philosophy to invent a new name for a long-established concept, even if that new name might somehow better reflect the concept
We don't need to invent a new name to use namespace, see C++, C#, and PHP.
Now, remember that static objects are going to be a fringe, rarely used feature.
I see too much effort for a "rarely used feature", I don't think this feature will be used rarely, you wrote: "In terms of teaching, companion objects will need to be moved to "advanced concepts" as novice programmers will rarely use them", and I agree with this sentence.
To minimize the number of concepts in the language, it has to reuse the concept of statics
"static" look like a new concept in Kotlin to me, this discussion is about adding it to the language.
from keep.
Not sure if the naming debate is fully closed, but I've been thinking: "static" to me is a reference to an implementation detail rather than an expression of purpose.
When I am writing a function or value and feel the need to nest it somewhere (rather than have it as a top-level element) the problem I am trying to solve is usually one of disambiguation - e.g. I don't want my Json.parse(string:String) to be confused with my Xml.parse(string:String). The matter of whether something is statically allocated (as opposed to e.g. lazily allocated) is a secondary consideration.
Also. One thing I'm eventually hoping for is to be able to add classes to a namespace, regardless of what file i'm in. - e.g. Json.Parser(). Here the static word would just be plain confusing - as the class itself isn't static, I just want to differentiate it from Xml.Parser(). Making a declaration like class Json.Namespace.Parser() { .... }
is intuitive and conveys the correct meaning. class Json.static.Parser()
or static { class Parser() }
is misleading - e.g. can I instantiate it or not?, is it statically allocated? - no. i just want to be clear what kind of thing is being parsed.
from keep.
Let's use this issue for an ad-hoc voting on the syntax via reactions:
Use 🎉 reaction to vote for static sections syntax.
Use 🚀 reaction to vote for static modifiers syntax.
Why don't both? If I have multiple entries, I'll use the static section, otherwise the modifier
from keep.
Great proposal. Only one observation for the JVM mangling scheme - $
is not that readable, but the other proposals might be confusing e.g. I wouldn't immediately think that getBackgroundColor
would map to Color.static.background
if I saw it in a stack trace.
Other alternatives:
Color_background
Color::background
(:
is an allowed character in JVM unqualified names)static extension Color getBackground
(spaces are allowed in JVM unqualified names)
from keep.
If there a one or few static members, it creates unnecessary nesting (indentation level).
If there are many static members, the function signature does not inform you if it is an static member: requires developers to check for a static block to understand it.
One thing I found frustrating when I work with Java statics is that they're on the same level with instance fields or methods, but that's not true from the point of view of functionality because they are just there having nothing to do with instances of their containing class. By adding another indentation - like all the other nested structures (excluding inner class
because it has an explicit inner
declaration) - it acts like a hint to the user that content within this block has no this
referring to the instances of the outer class. If the block is too big, then you always need to scroll up to see what the container is, no matter it is a static block or what. If another indentation for static block is bad, then all the other nested structures are somehow design mistakes.
from keep.
@Peanuuutz They both are not new. We already have namespaces (packages, object scopes) and we already have static dispatch as @fvasco noted. The question is about naming. But it is important, because naming influences how the feature is perceived by new users. static
looks like "like in java, but different". You will always need to reverse to Java knowledge to explain where does it come from. Also static objects (namespaces) do not exist in Java, which will confuse things even more. Reads like "it is a static field, but an object".
Namespaces on the other hand formally declare a concept of namespace (which already exist). And a possibility to create a namespace without creating a package. Static blocks inside a class are just namespaces bound to class name. In my opinion this explanation and model are much more clear.
from keep.
Another suggestion - the distinction between object
and static object
is unfortunate. Complexity that will exist forever. Is it maybe not possible to just automatically change how it's compiled depending on whether the object actually contains any state. To avoid binary compatibility breaks on the JVM, the instance and forwarding methods can just always be generated unless you explicitly opt out. That would avoid the need to explain the difference which could be difficult, especially to people learning programming for the first time.
from keep.
@CLOVIS-AI said everything very eloquently. I want to emphatically second the point that statics are inherently confusing and adding them would significantly undermine the "Kotlin as a first language" goal. I had trouble both understanding statics and teaching them to students, and even now I don't find them conceptually appealing. For example, their relationship with inheritance is not clear at all, whereas with companion objects, there's literally nothing to explain. In Python, it's possible to call the static or class method on an instance, which is terrifying, but people sometimes rely on that, unfortunately. The point is, statics have a lot of baggage and you have to learn them anew for each new language anyway.
There's one more thing to add though: the use cases section fails to sell the idea to me. As of writing, as stated in the document, the goals are:
- Enable static extensions of 3rd party classes [...]
- Provide a lighter-weight mechanism [...].
- Simplify interoperability with JVM statics that Kotlin compiler has to support anyway [...].
Goal 3 is, in my opinion, barely worth mentioning, as tweaking which particular ABI gets produced is an advanced task and requires one to know what they are doing, whether the language introduces "static" things or not. If statics do get accepted into the language (and I hope they don't), this goal is nice to have solved automatically, sure, but I don't think it can be used as a rationale.
The same with goal 2: it's purely performance optimization. Unless the language is dedicated to performance-sensitive tasks, it's tough to justify adding language features for the sake of performance.
Goal 1 is the most important one, the way I see it. There's a real need to have static extension functions, and the notion is completely natural: if there's a way to have Long.MAX_VALUE
, there should be a way to always have an extension val Long.Companion.MAX_JS_SAFE_VALUE
. However, it does not need static functions or any changes to the companion objects. This is purely a question of name resolution: what to use if you have Long.MAX_JS_SAFE_VALUE
in your code. There's nothing preventing us from saying that the companion section is always implicitly present, whether or not the companion object was defined, with the limitation that things like fun Long.Companion.parse(string: String): Long
can't ever access this
.
So, it would be nice if the rationale section was expanded to explain why these goals justify migrating from a well-established concept widely used throughout the ecosystem to a more obscure one that unfortunately happens to be the status quo in the mainstream languages, as mentioned in https://github.com/Kotlin/KEEP/blob/statics/proposals/statics.md#the-alternative-of-fixing-companion-objects.
from keep.
fun namespace<Int>.foo()
IMO this is not intuitive compared to Int.Companion.foo
, Int.static.foo
or Int.namespace.foo
. Even more if namespace
is a block or it is used in a similar way to context receivers (parenthesis).
If the primary use case on this topic is to add the possibility to create extension functions, I think most users would prefer that the companion object
just exists for all classes even if it is not explicitly declared.
from keep.
Thanks for the proposal, this looks very promising. Personally, I lean towards static sections, the only thing I don't appreciate is the verbosity of a single static constant property declaration. I understand that supporting both static sections and static modifiers generally is not an option, but what if this would be dependent on the declaration type?
E.g., static sections could be required for functions, extensions and getters, whereas only static constant properties could support the static modifier syntax (exclusively, or in addition to static sections). This would solve the verbosity issue and only slightly increase complexity. The code style discussion remains, but for constants only it isn't as impactful, from my perspective.
from keep.
Will companion objects be deprecated?
It is a non-goal of this proposal to deprecate or to completely replace companion objects.
from keep.
How about virtual object
?
In a sense that you code it like object but it does not actually exist in memory.
from keep.
I still don't understand why introducing everything using namespace but choosing static
instead...
Yeah namespace is not a "new" concept, as package has the same effect, but namespace
allows you to create a namespace in a file, not just another package. Since in Kotlin you can't do something like this:
import org.example.app
app.function()
which exists in languages such as Python, or you'd need to write the whole bloated package path, namespace
fixes this issue smoothly without going too far against those Javaer's experience, and this feature is actually present in languages like Typescript, C#.
Static, on the other hand, sounds more like implementation detail to me. When static
was introduced, it keeps emphasizing the idea of "this variable/function is shared in a certain scope, and has no process/instance specific effects", but in my opinion this is just how namespace sounds like, as in practice you use these variables/functions prefixed with the name of the corresponding scope, no matter how they ultimately result in, compiled down to globals/statics or what. inline
class to value
class already proves the advantage of abstraction, so this principle should be obeyed throughout the development, especially when namespace
fits better in as I said above.
from keep.
object
is a singleton: it is a single instance of a class, so its name is the instance accessor and its type simultaneously. It can be passed to function as any object, it can be a type argument and can implement interfaces and inherit classes, and it can have properties with backing fields.
This refers to object
s, data object
s and even value object
s (but backing fields) if they will somewhen be implemented. static object
s are totally different, you cannot simply replace an object
with a static object
, you cannot pass it to a function in any of roles (type or value argument), it is neither a type name nor instance.
The only common thing is accessing it when it is used as a receiver. Static objects are not objects at all, it is a categorial mistake to call them so. The name namespace
fits their purpose much better. This name is also used in C# and C++.
According to the choice between static blocks and members I would prefer having both as I can use both expression body and block body for function bodies. The first is short and very useful in the often case, where the body is single-line and short, the latter is useful in the rest of the cases.
But if I had to choose, I would choose member syntax because block syntax adds one more scope that is actually invested: it is usual, that the inner block is, the more it can access. However, in this case, members of the non-static block can access both static and instance methods while inner static methods can only access themselves.
Mentioned static interfaces feature would also benefit a lot from Self
-type feature.
from keep.
@edrd-f I quite like the syntax that was presented at Kotlin Conf a couple years ago:
val namespace<Intent>.SCHEME_SMS: String get() = "sms"
Which is much like the existing super
syntax, for disambiguating between superclasses:
super<SuperClassName>.someFunction()
It's not perfect, because it reads like a generic type, but there's some precedent.
But using namespace as keyword:
namespace(android.content.Intent) const val SCHEME_SMS = "sms"
Plus I'm not really a fan us using "context reciever"-like syntax for extension receivers. I would think this can't be accessed like Intent.SCHEME_SMS
, since it doesn't read like an extension property. (Since context receivers can't be called with dot-syntax.)
from keep.
Why don't both? If I have multiple entries, I'll use the static section, otherwise the modifier
Static sections don't make sense because all other modifiers can't be used with section syntax, so this will lead to an inconsistency. For example, it's impossible to do private { fun foo(...) { } }
. Why static
(or better, namespace
, please) should be different?
from keep.
@elizarov top level namespace scope actually works exactly like package does.
package a.b.c
fun myFunction(){}
...
a.b.c.myFunction()
Is almost the same as
package a.b
namespace c{
fun myFunction(){}
}
...
a.b.c.myFunction(){}
It makes sense to unify them.
Also there was a lot of discussions in Telegram. The consensus is that static
word is confusing. It is even more confusing with java background, because it works like in java in one case (keyword static), but not in others (class and top-level scopes). Also static
does not make sense in Kotlin because we do not have everything is a class/object
concept.
from keep.
Namespacing
Static object is a performance-oriented tweak to the concept of a named object. It need not be taught to beginner Kotlin developers. When used properly, one does not need to understand what difference static modifier for a named object makes.
In all discussions I have read with the Kotlin language designers, "is just a performance-oriented tweak" has almost always been an argument against the addition of a language feature, instead preferring an annotation or a compiler flag. I'm surprised it is now used for the opposite reason.
Static objects cannot implement interfaces nor extend classes. Their only intended purpose is to serve as a namespace grouping related functions.
They are not objects, and have a completely different use case than the existing companion objects. Reading the other comments in thread, it seems the majority agrees with this. I do not think a single solution to tie extensions without objects and top-level namespacing is a good idea.
The problem being namespacing, their implementation using static should be an implementation detail of the platforms supporting it. Looking at the features they would have, we can separate them in two:
- its identity has no meaning
- there's only one of them
Kotlin already has concepts for both: "there's only one of them" is object
, "its identity has no meaning" is the value
in value class
. Value classes behave very similarly to the proposed static objects: the compiler is allowed to substitute their value by another one as long as it is not observable (inlining and boxing automatically). A value object
, being a singleton, can be semantically destroyed and created from nothing. It is thus legitimate to expect a method of a value object
to be callable without providing any object at all. This seems at least as powerful as static objects to me, while tying the namespacing to existing Kotlin concepts. Value classes already cannot participate in inheritance, so again, all restrictions are implied by the restrictions of the individual concepts. "Members of value objects are static on the JVM" becomes a performance optimization (which this proposal states as the objective) without needing to introduce the concept of static to the language.
This is essentially the treatment of zero-sized types in Rust.
However, even this is limited by static objects forbidding this
, which even value object
have no reason to forbid. They really are a namespacing feature, and are not objects. If it really needs to be implemented like this, I do prefer top-level "all static blocks" to be called namespace
rather than static object
.
[…] the spirit of Kotlin design that tries to minimize the number of core concepts, opting to modify them instead. Kotlin does not have enum, but enum class, Kotlin does not have record but data class, etc.
I agree with this objective, but they are too different from objects, even when taking into account what a value object
could be. I think it would be a lot less confusing to add a namespace
keyword rather than use an existing keyword for something this different.
from keep.
The concept mentionned in the second part of this proposal, in my opinion, is so far from being an object, that it really shouldn't be called as such.
@CLOVIS-AI I agree with you, namespaces, like packages, are not objects.
from keep.
Re: static section vs static modifier
With static sections, a static function extracted from instance function will have to be moved to the static section, losing VCS change history of the body. Extracting a static function with a modifier produces only a small change in declarations without affecting the body of the function.
Before:
class X {
// more class members
fun foo(p: P) {
...
body()
...
}
// more class members
// static section
static {
}
}
After:
class X {
// more class members
fun foo(p: P) {
extracted(p) // +
} // +
// +
static fun extracted(p: P) { // +
...
body()
...
}
// more class members
// static section
static {
}
}
If a static section will be chosen, then it should be possible to have multiple static sections. This way an unnecessary indent would be still produced, but it's usually ignored by sane diff tools:
class X {
// more class members
fun foo(p: P) {
extracted(p) // +
} // +
// +
static { // +
fun extracted(p: P) { // +
.. ... // whitespace change
.. body() // whitespace change
.. ... // whitespace change
.. } // whitespace change
}
// more class members
// static section
static {
}
}
from keep.
I agree with others about the
static
modifier: if I want to create a namespace, then I need to define astatic object
(a class and its instance without the instance).
We can define astatic object
as a top-level object, but it isn't possible to define acompanion static object
, further this KEEP allows us to define scatteredstatic
sections in a class to add methods to the class's namespace.In the section Static object primer
The goal of this object is to ensure that call-sites for the declarations inside it, like notNull(), are prefixed with a namespace Delegates and look like Delegates.notNull()
Multiple times in the KEEP the "namespace" word has been used to explain the goal of a
static object
, in the last part of the document was explained that other languages use thestatic
keyword, but C++, Java, C3, JS/TS, Python, Swift, and Rust don't usestatic
to define packages, namespaces or, more in general, a group of declarations.
Instead, C# usesnamespace
to define a group of declarations, possibly nested, and this is omitted.From my point of view,
namespace
is the name of this feature.
Inside a class, instead of astatic
section, we should define at most onecompanion namespace
and possibly other namespaces:class HttpClient { companion namespace { const val HTTP } namespace Responses { fun string() } }
And maybe, namespace object { ... }
? This naming will match the usual Kotlin way of making anything that stuff that compares to static methods in other languages can be declared in being named object
. The companion can he called, companion namespace object { ... }
.
Basically what I'm suggesting - making namespace
the same as value
, both functionality-wise and syntax-wise.
from keep.
I see there's suggestion for further improvements to use with(Color.static) {}
to use static functions without prefix: https://github.com/Kotlin/KEEP/blob/statics/proposals/statics.md#static-scope-projection-reference
Does it mean that regular with(Color) {}
will allow only using companion object functions without prefixes, but still would require Color.
to call static ones?
from keep.
I like the static
modifier more; it is more aligned with other languages and directly clear from the function signature if the function is static. With the section, you have to check its location in the class, if this function is static (the same is true for the current compaction object
section).
Although I see the current votes, the static init {}
of the modifier syntax could be simplified to static { }
, making this more symmetrical to init {}
and to Java.
Another thing:
How do you call a static extension function extending "the static section" of another class:
class Example {
static fun AnotherClass.static.foo4() {}
}
What's the use-case allowing this?
from keep.
Looks promising, and no matter how the open issues are resolved, this will definitely be an enrichment for the language!
My thoughts on the static section vs static modifier question:
- While easier migration may seem like a compelling argument, modern IDEs like IntelliJ can easily provide a quick fix to convert companion objects with one click. The long-term disadvantages of an unusual syntax are more significant than a one-time migration issue.
- Static modifiers would simplify the language in my view and bring Kotlin closer to other programming languages. In contrast, static sections would look like static initializers to programmers of other languages.
- Static sections are more visually bloated, and with large sections, it may be difficult to tell which methods are static. This is a disadvantage that already exists with bloated companion objects, and it can hinder code readability.
- Grouping is optional in both cases, since multiple static sections can be created.
- The decision for static sections may lead to developers preferring to write static extensions even if it would have been possible to implement them as static methods:
fun Example.static.foo3() {}
fun Example.static.foo4() {}
class Example {
static {
fun foo1() {}
fun foo2() {}
}
}
from keep.
@streetsofboston @mikehearn @mcpiroman @sandwwraith @spen37 @kyay10 @Mr-Pine @dovchinnikov @edrd-f @sprigogin I've completely missed the fact that static object
is bound to cause controversy and did not provide background information on this choice in the design document. I've fixed it now.
I've added a short note at the end of Static objects section and added a whole new section on Static object alternatives and namespaces where I give an in-depth explanation of why namespace
did not make a cut. Please, read it.
TL;DR: Think of relation between static object
andobject
in a similar way as about relation between value class
and class
— a sort of performance-optimizing feature for experts to use. It might seem that "having an identity" is an integral part of "being an object" in Kotlin, but it is not. E.g., 1
in Kotlin is an object, yet it does not have an identity. Also, keep in mind that the whole static object feature is the least important part of the proposal. It is there only to avoid abuse in the form of "static utility classes" (classes with static-only members). Being the least important part, it must not dictate the choice of concepts and naming for the rest of the design.
from keep.
When I see an object
, no matter if it had static
, I would most likely to read it as it is an object, but static
completely defeats the purpose of object
. value class
on the other side, can be used as a normal class
. I don't think these two "pairs" act the same.
If I got the choice, I'd rather not having it at all than exchanging performance with confusion.
from keep.
@dovchinnikov I think the main reason of the
static object
situation is unwillingness to get rid ofcompanion object
concept altogether. If one greps the usages of object in Kotlin codebase, they'll see that they are mostly used to group a bunch of related functions and properties together, so Kotlin, being a pragmatic language, should respect this and make this the default.
Statics are specifically designed to be lighter-weight than companion objects both in terms of declaration (just static {}
even in static section syntax vs companion object {}
) and on the implementation side. It is totally expected that statics will become "the new default", effectively getting rid of the majority of uses for companion objects. In terms of teaching, companion objects will need to be moved to "advanced concepts" as novice programmers will rarely use them.
If Kotlin had namespaces/statics from the day 0, then
companion object
would be a weird concept, which has a single unobvious use case: an ability to use a class reference as a singleton instance of another type. I doubt it'd be added then.
Companion objects are important for some DSLs. Kotlin is a DSL-oriented language and has many DSL-centric features, not just companion objects. Maybe we will find another way to meet the needs of those DSLs without companion objects in the future through some more advanced static interfaces. If so, then we'll be able to deprecate the companion objects, but not right away.
from keep.
Reusing "companion" looks good for me, use a case to define different behaviors no, it is really misleading and difficult to teach.
I put "companion fun Foo.bar()" on the plate, same syntax of other extension functions.
from keep.
From the proposal:
Not an option: Support both static section and static modifier syntax.
Note, that supporting both static section and static modifier syntax is not an option, especially in the initial support of statics in Kotlin. This would move the choice of syntax to the coding style, which would make both the problem of picking a more suitable syntax and the problem of learning the Kotlin language even more complicated.
I am not sure if I agree with the reasoning. Kotlin already gives a lot of choice to the developer. Can't we come of with wording that would be not too confusing, while supporting both syntaxes?
Here's the thing, I like the idea of the static sections for all the reasons mentioned in the proposal. I would love to continue using it for stuff like constants. But then I often find myself writing things like this:
enum class Algorithms(val string: String) {
Sha256("sha256"),
Sha512("sha512");
companion object {
fun fromString(string: String) = Algorithms::string.findOrNull(string)
?: throw IllegalArgumentException("Unsupported algorithm: '$string'")
}
}
Using namespace {}
or whatever would not make this much readable. Besides, this factory method semantically belongs with the constructors, which are not placed in namespace {}
. It seems to me that based on what I want to express, there'll be a clear choice of what syntax I want to use.
(Or maybe we can have named constructors?)
from keep.
Great proposal. The idea of statically implementing an interface is interesting, but I wonder if
static interface
is the right concept. It implies that the interface "knows" that it will be (must be) implemented statically. An alternative would be:interface Parseable<T> { fun parse(s: String): T } class Color(val rgb: Int) : static Parseable<Color> { static { /*override*/ fun parse(s: String): Color { /* impl */ } } }
I like that style of static implementation, but If the interface does not know if it will be implemented statically or not, what would happen if illegal this
references were made in it?
Example:
interface Parseable<T> {
fun parse(s: String): T
fun something() {
println("I am $this") // no instance of `this` statically
}
}
from keep.
Great proposal. I've been waiting for this since I started using Kotlin.
Few observations:
- The "It is easy to migrate existing companion object declarations to statics" argument in favor of static sections would not apply if IntelliJ offers a refactoring that does this conversion.
- It would be more concise and intuitive if
static object
were replaced withnamespace
.
Question:
- What is the rationale for the "Static section is not allowed inside an inner class" restriction? Is it due to the JVM bytecode limitation?
from keep.
But
1
is not a type, while anobject
is a type. When I see anobject
, I expect it to have a type. Maybe the average developer doesn't view it as such, I'm not sure.
@kyay10 If you grep the usages of object
in Kotlin codebase, you'll see that they are mostly used to group a bunch of related functions and properties together. Both anonymous object {}
and named object XXX {}
are used for that. A property of "having a type" is truly secondary and rarely used.
Btw, I've also added a note on "local package" alternative for static objects to the text. It was on the table at some point, but I forgot to mention it.
from keep.
@elizarov but on sealed classes and interfaces, the object
branches are more a type than a group of properties or functions.
from keep.
Given that
We expect a large number of companion objects to be migrated to statics
and
If you grep the usages of object in Kotlin codebase, you'll see that they are mostly used to group a bunch of related functions and properties together
then why
remember that static objects are going to be a fringe, rarely used feature
? Those three statements do not add up to me.
Migrating companion object
-> static
and object
-> static object
both give the same benefits (reduced footprint) and are very symmetrical, so why is one of them expected to be frequent and the other rare?
from keep.
One of the features I was hoping the new namespace feature would have was the ability to add new class definitions within a namespace - e.g. class String.Matcher. Is that still a possibility? I can't find any discussion of that and the use of the static keyword may confuse things as it implies overlap with java concept of static class.
from keep.
@stantronic Isn't that already possible with nested classes?
No, unless. i'm missing something. I can't add a nested class to String as I don't own String. Furthermore, nested classes impose the restriction of needing to be added within one file and with potentially less-readable indentation (This is an issue we have a big problem with in out codebase at the moment where nested classes are used for namespace disambiguation - you can't tell exactly what belongs to what without scrolling up and down). What I have in mind would be something like extension classes, similar to how we now have extension functions and extension properties.
from keep.
I like how Future improvements part of this KEEP changes the usage of interfaces for static functions and properties.
A companion object can implement interfaces and that is probably a nice feature for frameworks. Say, one implements some non-standard serialization. Required serialization details like type keys or custom serializers can be implemented as members of companion objects. And verifying that all details are present can be done by making companions implement an interface:
interface SerializableClass {
val serializationKey: String
}
class Product(
val id: Int,
val name: String
) {
companion object : SerializableClass {
override val serializationKey: String = "pr_products"
}
}
However, even though being serializable here is a property of Product
class, it doesn't implement SerializableClass
. Instead, its companion object implements it. And with static overrides from Future improvements in this KEEP, this interface would be implemented by the class itself, not its companion. I think this is more consistent.
static interface SerializableClass {
val serializationKey: String
}
class Product(
val id: Int,
val name: String
) : SerializableClass {
static {
override val serializationKey: String = "pr_products"
}
}
from keep.
@fvasco Companion namespace is a very good idea. It allows to create non-contradictory mental model with only one additional work. It does not allow single "static", only group declaration. But it seems that people are mostly in favor of group declaration anyway.
from keep.
@fvasco Good point about extensions already being static. I agree that the term static dispatch (opposite to dynamic dispatch) makes a lot of sense and I use it a lot in lectures. Static keyword would make it much more confusing.
from keep.
To help in the discussion about namespace, I tried to categorize the elements that can be defined in Kotlin.
This resulted in the following categories: namespace scoped, instance scoped, and local scoped.
The following table can be seen as a matrix where
- the y-axis (top to bottom) lists Kotlin's elements (grouped by the categories) and
- the x-axis (left to right) lists the scopes where elements can be defined and
- the fields show the Kotlin keywords used to define the elements in the specific scope.
Examples, how to read the table/matrix:
- The first column shows that
package
is a namespace scope and can define top-level functions, properties, classes and interfaces, which are all namespace scoped elements. class
is an instance scope that can define instance functions (usually known as methods), instance fields and inner classes, all of which can access the instance (instance scoped elements). But theclass
column also shows that it is already possible to use this scope to define namespace scoped elements: nested (not inner) classes and objects. In this case, the class name acts as a namespace.
[Note: Initially I created this table to use it as an argument that Kotlin should add namespace scoped functions and properties to fill the gaps, but thankfully this decision has already been made.]
The hatched cells are elements that can not be defined in that scope or are semantically nonsense.
Column 2-5, row 1-4 are the cells that will/should be filled by this KEEP.
The table/matrix is not 100% refined yet, but I wanted to share it already.
I am also not proposing any solutions for statics inside the table yet (for example naming static vs namespace), it should be just a representation/categorization of what is already possible in Kotlin to help in the discussion.
from keep.
But if I had to choose, I would choose member syntax because block syntax adds one more scope that is actually invested: it is usual, that the inner block is, the more it can access. However, in this case, members of the non-static block can access both static and instance methods while inner static methods can only access themselves.
Static block behaves similar to nested and inner, but not the same.
Check the following structure:
class Outer {
fun outer()
static {
fun static()
}
class Nested {
fun nested()
}
inner class Inner {
fun inner()
}
}
As we follow the principle above, we have the following results:
- outer -> static: Implicit reference (the class name
Outer
). - outer -> nested: Explicit reference.
- outer -> inner: Explicit reference.
- static -> outer: Explicit reference.
- nested -> outer: Explicit reference.
- inner -> outer: Implicit reference (the instance of
Outer
).
In general, it's more of a problem of what the reference is, and if the reference is explicit or implicit. I believe companion object
for static purpose will be replaced by static block because they share the similar characteristics.
from keep.
[...]
- (IMHO better) as mentioned above, replace
static object
withnamespace
, thus also havingcompanion namespace
(instead ofstatic
section).[...]
- There can be both
companion object
andcompanion namespace
in a class.- You (probably) can have multiple multiple
companion namespace
s in a class- The symmetry is still achieved with
namespace
andcompanion namespace
- You can have both named and companion
namespace
in a class, the same as you can now have both named and companion objects.- Like in 1), KT-11968 is solved by all classes implicitly having an empty
companion namespace
which can be extended.- Extensions are (probably) like in the KEEP -
fun MyClass.namespace.foo()
.namespace
in this context also looks slightly more meaningful thanstatic
,
+1. The symmetry of companion object
and companion namespace
is very nice because it keeps the mental model that Kotlin developers already have for companion objects.
For extensions, though, it's weird to have fun <Type>.<keyword>.<identifier>
. I don't remember seeing anything like that in the language and in syntax highlighters it would look strange. Instead, I'd borrow @CLOVIS-AI syntax idea:
// Alternative syntax, inspired by context receivers // This frees up the single receiver, which isn't used since it's not an object anyway companion(Foo) fun bar() = …
But using namespace
as keyword:
namespace(android.content.Intent)
const val SCHEME_SMS = "sms"
from keep.
Can someone explain me please the following: in the example Option: Static section syntax
, copied below:
class Outer(val one: String, val two: String) {
static {
fun createMappings(): List<String> =
setOf(::one, ::two).map { it.name } // WORKS! No need to write Outer::one, Outer::two
}
}
because createMappings
is a static, this means i don't need an instance of Outer
to call the static function.
So i can just do
val l: List<String> = Outer::createMappings()
My problem with this is that one
and two
and it.name
are instance variables of Outer, which means that they do not exist in my example above.
The example looks wrong to me, but I assume it's not wrong and I'm just misunderstanding something.
from keep.
This would conflict with Extension functions as static members but I agree with you and don't really like fun SomeClass.static.extension()
either
from keep.
@TheBestPessimist
This works because ::one
and ::two
are property references (KProperty1
s) , which don't have this
reference. As suggested, this is the same as writing Outer::one
(in any scope).
from keep.
Since "Extension functions as static members" is allowed, I think we absolutely should allow static operators that can be imported as you'd expect. That would allow more controlled scoping of operators.
Also, I agree with everyone that namespace makes a ton more sense over static. Obviously it's important to keep the language accessible to newcomers, but I think that static object
alone shows how inappropriate the word static
is for the Kotlin language.
Also, there's an unresolved question here about how you could define an extension static function that is itself an extension on some type (i.e. Int.foo
that is within the namespace of MyClass
). Perhaps that is a non-issue now, but with "Static scope projection reference", we should have support for using MyClass.static
as a context receiver.
Maybe also instead of the MyClass.static
syntax we could use something like namespace<MyClass>
that was shown as an early sketch. That could be read as namespace of MyClass
and would perhaps emphasise that this declaration is not extending a type in the ordinary sense but instead is extending a namespace of that type.
from keep.
I wanted this in Scala 3 to easily add extension methods for both scala's object
and Java classes which act as static members.
So +1
from keep.
@Mr-Pine
Ah yes I see. In that case I guess I agree with @sandwwraith's comment on calling it a namespace fun
. That way you could also potentially have something funky like a static namespace fun
😆
from keep.
Notes:
-
There is no mention on how does this KEEP corelate with KT-45587 Tuples (structural literals and structural types)
-
List.empty
andList.of
are nice, but if there will really be a migration oflistOf()
-like functions, then it should rather be directly to collection literals, lest there are 3 ways equivalent to create a list. -
In the example
Option: Static section syntax.
class Color(val rgb: Int) : Parseable<Color> { static { fun parse(s: String): Color { /* impl */ } } }
Shouldn't fun parse
be override
?
from keep.
@spen37 It looks like static namespace fun
is also possible with the suggested syntax as static fun Color.static.myExt()
or static { Color.static.myExt() }
. Not sure there are any compelling use-cases for that. Either syntax is confusing.
from keep.
I like namespace
instead of static object
but would still prefer static
in the context of static members.
Having a static extension as a static member will probably always be a little confusing because you have to signal, that it is "double static", but I like @kyay10's of declaring static extensions as static<MyClass>.myExtension()
(or namespace<>
, or whatever the final Keyword will be) although I'm not perfectly happy with that solution either since I think it will be confusing (at first) why an extension member in a static block (or with a static keyword) is not a static extension. So it may also make sense to consider a syntax for specifying that an extension that is a static member is not also a static extension
from keep.
As I like the grouping aspect that automatically came with companions, I highly favor the grouping syntax here. 😊
from keep.
I don't like the idea at all, static extensions are nice to have but not worth to add more complexity to the language (companion objects already provide most of the need) and this probably will impact kmm implementation
from keep.
Great write-up, but there doesn't seem to be any information about the ABI of platforms other than the JVM. What's the ABI of JS, and is it blocked/require ES2015 output from the compiler? Does the Objective-C interop use class methods or something else?
Also curious if external static function declarations work on JVM and JS?
@JakeWharton Thanks! I've added the ABI for non-JVM platforms section to the open issues. We don't have any design for them yet. Kotlin/JVM platform provides the strictest backwards and forwards compatibility guarantees, as well as seamless two-way interoperability, hence the JVM ABI is taken care of first.
from keep.
The concept of "static sections" could be generalized to "modifier sections" with equivalent private and internal sections. Each modifier section would then apply its modifier implicitly to all contained members.
@axelfontaine That would be a quite un-Kotlin way to approach things. This would add an extra freedom to the way you write your code that is bound to inflame code-style wars. Readability will suffer for an average piece of Kotlin code, because you'll never know if a modifier is truly absent before a declaration or if you have to look for a "modifier block" that changes it. Static sections are a special case because of their connection to static extensions.
from keep.
Great proposal. Only one observation for the JVM mangling scheme -
$
is not that readable, but the other proposals might be confusing e.g. I wouldn't immediately think thatgetBackgroundColor
would map toColor.static.background
if I saw it in a stack trace.
@mikehearn I've added the first two of your suggestions to the table in the corresponding section. From my experience, spaces in stack-traces would cause readability issues and might get misinterpreted by tools.
from keep.
Spaces also don't work on Android except on the absolute newest versions so I would entirely reject that as a possibility.
from keep.
1
in Kotlin is an object
But 1
is not a type, while an object
is a type. When I see an object
, I expect it to have a type. Maybe the average developer doesn't view it as such, I'm not sure.
It also feels like the use of static object
is a relic of the current use of object
for static grouping of things. If Kotlin was designed today, would static object
be the most apt solution? static object
is obviously subtractive, but it seems to subtract a lot of properties of object
, reducing it down to just a name. value class
is also subtractive, but it still keeps some fundamental properties of a class
.
from keep.
Overall a good proposal. I had considered leaning more upon the compiler to make things static if possible (even if in the companion), but then considered that this would not be ABI compatible (except for private members). One aspect I hope is added is proper (private) static members in interfaces (as allowed by the JVM).
I have however one drawback. My experience is that "static" is not a word that is easily understood (by beginners), but I'm not sure what alternative to use either (perhaps type
although the lack of consistency in terminology with Java can also be a problem).
from keep.
I think the main reason of the static object
situation is unwillingness to get rid of companion object
concept altogether.
If one greps the usages of object in Kotlin codebase, they'll see that they are mostly used to group a bunch of related functions and properties together, so Kotlin, being a pragmatic language, should respect this and make this the default.
If Kotlin had namespaces/statics from the day 0, then companion object
would be a weird concept, which has a single unobvious use case: an ability to use a class reference as a singleton instance of another type. I doubt it'd be added then.
interface Key
interface Job {
companion object : Key
}
fun usage() {
println(Job) // object of type Key
}
from keep.
@stantronic Isn't that already possible with nested classes?
from keep.
If Kotlin had namespaces/statics from the day 0, then
companion object
would be a weird concept, which has a single unobvious use case: an ability to use a class reference as a singleton instance of another type. I doubt it'd be added then.
@dovchinnikov This is not correct. Companions are a powerful concept. They allow to use type itself as a key (just look how it is done in kotlinx-coroutines CoroutineContext keys). More importantly, companion objects could be used in future to implement companion contracts (that companion is forced to implement specific interface). It would close all remaining functionality of type-classes and add much more.
from keep.
companion namespace
One can only have 1 companion object
in the class, and it would be really useful to allow multiple companion namespace
sections, but then it would be inconsistent with companion object
. In this case namespace fun
might make more sense than companion namespace
sections.
from keep.
In the initial design presented in this KEEP, static methods cannot be overridden and cannot be dispatched dynamically, static objects cannot implement interfaces. This can be changed by introducing a concept of a static interface.
Can you please provide a deeper example for how overriding and dynamic dispatching shall work for static interfaces?
The example in the proposal has only one implementing class and leaves a lot of room for interpretation.
- I assume
Color.static is Parseable<Color>
butred !is Parseable<Color>
? - With
class HtmlColor: Color
do I getHtmlColor.static is Parseable<Color>
? - Will
HtmlColor.static
inherit fromColor.static
, but only those members inParseable
? - If
Color
isabstract
, can I haveabstract static
members inColor
?
Have you considered static interface members
as in C#11?
from keep.
I'd like to see something like context(A)
, like namespace(Class)
:
namespace(Locale)
context(Context)
val currentLocale: Locale get() = ...
class MyActivity : Activity() {
override fun onCreate(..) {
...
textView.setText(Locale.currentLocale)
}
}
it, first of all, fits previous design that was quite good and does not create a new one that makes declarations like context(A)
more logical.
also it looks much better and makes clear what functionality actually do. I find it terrible fr when we talk about it, for example, about extensions or top-level functions. Mainly, problem not in functionality, but in naming. Don't like it tho
from keep.
@rnett For static inheritance, it would also be nice to be able to mix static and non-static abstract methods
It requires a totally different approach. It is not a simple as you show in your example, as it will not be clear what is the meaning of val x: Serializable<Foo>
is (is it storing a reference to an "instance" of a serializable value or a reference to Foo's serialization factory?). You'll have to get rid of T
typeparameter in the interface altogether, introduce self-types, etc.
@quickstep24 Have you considered
static interface members
as in C#11?
Yes. That's essentially what @rnett asks about, too -- generally that same thing as C# does. This "static interface members" approach has its pros and cons. I will not go into all details here. Let's focus on the core parts of the proposal for now.
from keep.
Multiple times in the KEEP the "namespace" word has been used to explain the goal of a
static object
, in the last part of the document was explained that other languages use thestatic
keyword, but C++, Java, C3, JS/TS, Python, Swift, and Rust don't usestatic
to define packages, namespaces or, more in general, a group of declarations.
Instead, C# usesnamespace
to define a group of declarations, possibly nested, and this is omitted.
@fvasco Kotlin already has a concept that serves the same purpose as a namespace
in C# -- it is called a package
in Kotlin. So, the full analogy to C# approach in Kotiln is going to be to support some kind of "local packages". I've moved the explanation of this alternative to a separate subsection in the proposal: https://github.com/Kotlin/KEEP/blob/statics/proposals/statics.md#local-packages
from keep.
I really ike the proposal. static object
is the one thing that does feel odd. Of all the alternatives discussed, I prefer the named static section syntax. It's the syntax that came to my mind for this use case before I got to the static object
part of the proposal.
from keep.
UPDATES: I've added two new sections on code style to record an answer on the future role of companion objects that I've given here in a more permanent place, and to address an important style concern on static members vs extensions:
I've also explicity addressed the main alternative to the whole static proposal that have been mentioned many times in various issues: The alternative of fixing companion objects.
from keep.
Much controversy around data object
. It's not strictly necessary to introduce it, you could also just use this:
class Namespace {
static {
// ...
}
}
You could also add a private constructor, or use (sealed) interfaces.
Ok, there is also the comment on this in the proposal: "it will immediately cause the 'static utility class' boilerplate pattern to appear in the Kotlin code". But this stands in conflict with the other statement that "static objects feature is a fringe part of this design proposal ... only a few will use static objects". If this is true, having this minimal extra boilerplate seems to outweigh the burden of introducing a controversial new feature.
from keep.
Reusing companion
is a big-brain solution, but the more I think about companions, the less I feel like this word implies "something that doesn't involve this
", or to say a bit vague.
from keep.
@Peanuuutz I'm using companion
to mean “goes with the class” and object
to means “has this
”; thus companion
without object
doesn't have this
.
from keep.
@CLOVIS-AI No I mean this
referring to the instance of the accompanied class. But I kind of accept its meaning right now. :)
However, I found another problem. If you reuse companion for that case, you can't use it for static object
, thus introducing another keyword which refers to the same concept (or at least from the user's perspective), namespace
.
I think "namespace member" is not that weird, as it implies "these members are under this namespace, which doesn't have an actual instance".
from keep.
value object
isn't so nice to define namespaces for me, it is complex to explain and it can be confused with features in other languages.
If we don't want to introduce a new keyword (like "static"), I suggest package object
to define namespaces, it should be more intuitive for a Kotlin developer.
from keep.
@Peanuuutz said:
However, I found another problem. If you reuse companion for that case, you can't use it for static object, thus introducing another keyword which refers to the same concept (or at least from the user's perspective), namespace.
Yes, that's on purpose. I disagree that they are the same thing, and thus disagree that they should have the same syntax.
@fvasco said:
If we don't want to introduce a new keyword (like "static"), I suggest package object to define namespaces, it should be more intuitive for a Kotlin developer.
I take issue both with static
and with object
. The concept mentionned in the second part of this proposal, in my opinion, is so far from being an object, that it really shouldn't be called as such.
Again, package object
, to me, means "an object that behaves like a package". If it's an object, it must have this
and must be able to implement interfaces/abstract classes, can be put in a variable, inherit from Any
, can overload toString
, etc. The proposed concept is so different it really deserves its own naming.
from keep.
I heavily agree with @dkhalanskyjb
The "major shortcoming" in section https://github.com/Kotlin/KEEP/blob/statics/proposals/statics.md#the-alternative-of-fixing-companion-objects is poorly explained.
Companion object is a well established concept of the language. Major concept, as I can say. And throwing it away just because polishing and optimisation will make it look like "another name for static" is not a good argument. It's already an "another name for static", as I tell my colleagues from Java. So what? It has better mechanisms under the hood and better suits the language philosophy.
I'm not against static
keyword. I see where it's coming from - people use companions in cases where they want to use static members and functions. Places where @JvmStatiс
can be added without breaking anything are surely the "red flags" of this problem.
But that's not enough to deprecate companions completely or to introduce such a major language feature.
from keep.
TL;DR: Think of relation between
static object
andobject
in a similar way as about relation betweenvalue class
andclass
— a sort of performance-optimizing feature for experts to use. It might seem that "having an identity" is an integral part of "being an object" in Kotlin, but it is not. E.g.,1
in Kotlin is an object, yet it does not have an identity. Also, keep in mind that the whole static object feature is the least important part of the proposal. It is there only to avoid abuse in the form of "static utility classes" (classes with static-only members). Being the least important part, it must not dictate the choice of concepts and naming for the rest of the design.
But don't files serve this purpose already? Don't get me wrong - I love this feature - in fact, I think the static blocks should, too, be named static object {...}
and not just static
- but you can just do the following:
@file:JvmName("Utilities")
@file:JvmMultifileClass
package com.example.project.util
fun myStaticFunction() = Unit
These are the prime spots for putting statics. Well, ones that aren't bound to a type. I hope these will not be deprecated?
// Kotlin
import com.example.project.util.*
// Java
import com.example.project.util.*;
import com.example.project.util.Utilities.*;
from keep.
To elaborate on The feature that every Kotlin developer will use are static members, some will also use static extensions, and only a few will use static objects
:
The use case of static objects is performance. But I think that, maybe unlike other features such as value classes, applying static
to a given object changes very little. Each object brings some overhead, mostly binary size, that is not very visible when actually using the object on its own (I assume JVM is smart enough to eliminate the unused this
parameter), but instead they slowly add up to affect the whole experience.
Therefore to actually fight that it is only sensible to mark all eligible objects as static. So either everyone in the team uses them, or we don't think (or don't know) that regular objects affect us meaningfully and we don't use static objects at all.
Thus it also makes more sense to make this feature the default - such as it would have been with namespace
/package
. Or, if the effects are mild, so it becomes rarely useful, maybe make it lower-level, such as an annotation of compiler flag.
from keep.
I just realize how does the migration of companion object extensions to static extensions look like? I assume it won't break source compatibility, but may break binary compatibility.
// from
fun Int.Companion.foo()
// to
fun namespace<Int>.foo()
from keep.
@oakkitten The big difference between companion objects and namespaces is that fundamentally (because of ABI compatibility) members of companion objects can not be static. The static annotations as currently employed just forward to the non-static members. I like companion object in its conceptual clarity (and I dislike the word static for the opposite).
from keep.
@edrd-f Namespaces are about to replace most of the objects and companion objects. I'd say it's easier and more reasonable to organize static members, but not others like different visibilities. Also, other modifiers affect how you access the declaration, while static affects where you access it. They are different for sure.
namespace Foo {
fun function()
}
class Foo {
namespace {
fun function()
}
}
from keep.
Related Issues (20)
- java.util.Optional support in the standard library HOT 4
- Easier custom iterables with cleanup support HOT 1
- Support non-local break and continue inside inline lambdas HOT 7
- Support references to synthetic Java properties
- Why interface does not support protected modifier? HOT 1
- Multi-field value classes HOT 28
- Ambiguous type argument lists
- Proposal for ResettableLazy, ResettableLazyUnSynchronized - HOT 2
- Split Classes HOT 1
- Kotlin Usigned Operations HOT 3
- Request: offer some way that the language could enforce uniqueness of constants inside a class/interface HOT 2
- HexFormat API HOT 2
- Infinite loop HOT 12
- Context parameters HOT 328
- Guards HOT 55
- Base64 HOT 3
- Multi-dollar interpolation HOT 23
- Improve resolution using expected type HOT 41
- Warning when using Object's default toString HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from keep.