Coder Social home page Coder Social logo

Comments (122)

sandwwraith avatar sandwwraith commented on August 24, 2024 57

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 objects.

from keep.

streetsofboston avatar streetsofboston commented on August 24, 2024 39

Great proposal!

Few concerns that popup in my head.

Will companion objects 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 objects 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.

SPC-code avatar SPC-code commented on August 24, 2024 35

(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.

axelfontaine avatar axelfontaine commented on August 24, 2024 34

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.

sandwwraith avatar sandwwraith commented on August 24, 2024 22

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.

mcpiroman avatar mcpiroman commented on August 24, 2024 19

Alternative proposals:

  1. instead of static section have static companion object, so that it is symmetric with static object, or
  2. (IMHO better) as mentioned above, replace static object with namespace, thus also having companion namespace (instead of static section).

Pros of 1):

  • Symmetry: you can mark both objects and companion objects static, both having the same semantics (and also the same as in KEEP).
  • Symmetry in extensions: both static and regular companion objects can be extended with the same syntax (fun MyClass.Companion.foo(). KT-11968 is solved by all classes implicitly having an empty static companion object. It can be then explicitly replaced with user-specified one, either static or not.
  • Less new concepts in language (static keyword only on objects, no new static 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 with static sections.
  • static companion object is rather lengthy.
  • static companion object looks kind of like static classes in Java - confusing and boilerplatey

But those cons are solved in option 2):

  • There can be both companion object and companion namespace in a class.
  • You (probably) can have multiple multiple companion namespaces in a class
  • The symmetry is still achieved with namespace and companion 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 than static,

from keep.

JakeWharton avatar JakeWharton commented on August 24, 2024 18

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.

dovchinnikov avatar dovchinnikov commented on August 24, 2024 17

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.

CLOVIS-AI avatar CLOVIS-AI commented on August 24, 2024 16

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.

quickstep24 avatar quickstep24 commented on August 24, 2024 15

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.

mikehearn avatar mikehearn commented on August 24, 2024 14

@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.

fvasco avatar fvasco commented on August 24, 2024 13

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.

spen37 avatar spen37 commented on August 24, 2024 12

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.

rnett avatar rnett commented on August 24, 2024 11

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.

mcpiroman avatar mcpiroman commented on August 24, 2024 10

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.

SPC-code avatar SPC-code commented on August 24, 2024 9

I also like MyClass.namespace.something() much better than MyClass.static.something().

from keep.

Dreiko avatar Dreiko commented on August 24, 2024 8

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.

edrd-f avatar edrd-f commented on August 24, 2024 7

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.

marcellogalhardo avatar marcellogalhardo commented on August 24, 2024 7

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.

fvasco avatar fvasco commented on August 24, 2024 7

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.

stantronic avatar stantronic commented on August 24, 2024 7

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.

elect86 avatar elect86 commented on August 24, 2024 7

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.

mikehearn avatar mikehearn commented on August 24, 2024 6

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.

Peanuuutz avatar Peanuuutz commented on August 24, 2024 6

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.

SPC-code avatar SPC-code commented on August 24, 2024 6

@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.

mikehearn avatar mikehearn commented on August 24, 2024 5

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.

dkhalanskyjb avatar dkhalanskyjb commented on August 24, 2024 5

@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.

JavierSegoviaCordoba avatar JavierSegoviaCordoba commented on August 24, 2024 5
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.

Maxr1998 avatar Maxr1998 commented on August 24, 2024 4

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.

mcpiroman avatar mcpiroman commented on August 24, 2024 4

@streetsofboston

Will companion objects be deprecated?

It is a non-goal of this proposal to deprecate or to completely replace companion objects.

from keep.

mcpiroman avatar mcpiroman commented on August 24, 2024 4

How about virtual object?
In a sense that you code it like object but it does not actually exist in memory.

from keep.

Peanuuutz avatar Peanuuutz commented on August 24, 2024 4

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.

zhelenskiy avatar zhelenskiy commented on August 24, 2024 4

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 objects, data objects and even value objects (but backing fields) if they will somewhen be implemented. static objects 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.

BenWoodworth avatar BenWoodworth commented on August 24, 2024 4

@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.

edrd-f avatar edrd-f commented on August 24, 2024 4

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.

SPC-code avatar SPC-code commented on August 24, 2024 3

@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.

CLOVIS-AI avatar CLOVIS-AI commented on August 24, 2024 3

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.

fvasco avatar fvasco commented on August 24, 2024 3

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.

dovchinnikov avatar dovchinnikov commented on August 24, 2024 3

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.

DanielGolan-mc avatar DanielGolan-mc commented on August 24, 2024 3

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()
  }
}

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.

sandwwraith avatar sandwwraith commented on August 24, 2024 2

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.

hfhbd avatar hfhbd commented on August 24, 2024 2

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.

SageDroid avatar SageDroid commented on August 24, 2024 2

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.

elizarov avatar elizarov commented on August 24, 2024 2

@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.

Peanuuutz avatar Peanuuutz commented on August 24, 2024 2

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.

elizarov avatar elizarov commented on August 24, 2024 2

@dovchinnikov 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.

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.

fvasco avatar fvasco commented on August 24, 2024 2

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.

oakkitten avatar oakkitten commented on August 24, 2024 2

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.

deotimedev avatar deotimedev commented on August 24, 2024 1

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.

sprigogin avatar sprigogin commented on August 24, 2024 1

Great proposal. I've been waiting for this since I started using Kotlin.

Few observations:

  1. 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.
  2. It would be more concise and intuitive if static object were replaced with namespace.

Question:

  1. 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.

elizarov avatar elizarov commented on August 24, 2024 1

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.

@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.

JavierSegoviaCordoba avatar JavierSegoviaCordoba commented on August 24, 2024 1

@elizarov but on sealed classes and interfaces, the object branches are more a type than a group of properties or functions.

from keep.

mcpiroman avatar mcpiroman commented on August 24, 2024 1

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.

stantronic avatar stantronic commented on August 24, 2024 1

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 avatar stantronic commented on August 24, 2024 1

@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.

TarCV avatar TarCV commented on August 24, 2024 1

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.

SPC-code avatar SPC-code commented on August 24, 2024 1

@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.

SPC-code avatar SPC-code commented on August 24, 2024 1

@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.

SgtSilvio avatar SgtSilvio commented on August 24, 2024 1

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 the class 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.]

namespace-current drawio

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.

Peanuuutz avatar Peanuuutz commented on August 24, 2024 1

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.

edrd-f avatar edrd-f commented on August 24, 2024 1

@mcpiroman

[...]

  1. (IMHO better) as mentioned above, replace static object with namespace, thus also having companion namespace (instead of static section).

[...]

  • There can be both companion object and companion namespace in a class.
  • You (probably) can have multiple multiple companion namespaces in a class
  • The symmetry is still achieved with namespace and companion 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 than static,

+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.

TheBestPessimist avatar TheBestPessimist commented on August 24, 2024

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.

Mr-Pine avatar Mr-Pine commented on August 24, 2024

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.

mcpiroman avatar mcpiroman commented on August 24, 2024

@TheBestPessimist
This works because ::one and ::two are property references (KProperty1s) , which don't have this reference. As suggested, this is the same as writing Outer::one (in any scope).

from keep.

kyay10 avatar kyay10 commented on August 24, 2024

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.

He-Pin avatar He-Pin commented on August 24, 2024

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.

spen37 avatar spen37 commented on August 24, 2024

@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.

mcpiroman avatar mcpiroman commented on August 24, 2024

Notes:

  • There is no mention on how does this KEEP corelate with KT-45587 Tuples (structural literals and structural types)

  • List.empty and List.of are nice, but if there will really be a migration of listOf()-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.

sandwwraith avatar sandwwraith commented on August 24, 2024

@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.

Mr-Pine avatar Mr-Pine commented on August 24, 2024

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.

JohannesPtaszyk avatar JohannesPtaszyk commented on August 24, 2024

As I like the grouping aspect that automatically came with companions, I highly favor the grouping syntax here. 😊

from keep.

anod avatar anod commented on August 24, 2024

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.

elizarov avatar elizarov commented on August 24, 2024

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.

elizarov avatar elizarov commented on August 24, 2024

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.

elizarov avatar elizarov commented on August 24, 2024

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.

@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.

JakeWharton avatar JakeWharton commented on August 24, 2024

Spaces also don't work on Android except on the absolute newest versions so I would entirely reject that as a possibility.

from keep.

kyay10 avatar kyay10 commented on August 24, 2024

@elizarov

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.

pdvrieze avatar pdvrieze commented on August 24, 2024

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.

dovchinnikov avatar dovchinnikov commented on August 24, 2024

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.

Mr-Pine avatar Mr-Pine commented on August 24, 2024

@stantronic Isn't that already possible with nested classes?

from keep.

SPC-code avatar SPC-code commented on August 24, 2024

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.

dovchinnikov avatar dovchinnikov commented on August 24, 2024

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.

quickstep24 avatar quickstep24 commented on August 24, 2024

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> but red !is Parseable<Color>?
  • With class HtmlColor: Color do I get HtmlColor.static is Parseable<Color>?
  • Will HtmlColor.static inherit from Color.static, but only those members in Parseable?
  • If Color is abstract, can I have abstract static members in Color?

Have you considered static interface members as in C#11?

from keep.

y9vad9 avatar y9vad9 commented on August 24, 2024

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.

elizarov avatar elizarov commented on August 24, 2024

@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.

elizarov avatar elizarov commented on August 24, 2024

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.

@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.

jeffdgr8 avatar jeffdgr8 commented on August 24, 2024

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.

elizarov avatar elizarov commented on August 24, 2024

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.

thumannw avatar thumannw commented on August 24, 2024

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.

Peanuuutz avatar Peanuuutz commented on August 24, 2024

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.

CLOVIS-AI avatar CLOVIS-AI commented on August 24, 2024

@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.

Peanuuutz avatar Peanuuutz commented on August 24, 2024

@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.

fvasco avatar fvasco commented on August 24, 2024

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.

CLOVIS-AI avatar CLOVIS-AI commented on August 24, 2024

@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.

mitasov-ra avatar mitasov-ra commented on August 24, 2024

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.

DanielGolan-mc avatar DanielGolan-mc commented on August 24, 2024

@elizarov

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.

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.

mcpiroman avatar mcpiroman commented on August 24, 2024

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.

Peanuuutz avatar Peanuuutz commented on August 24, 2024

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.

pdvrieze avatar pdvrieze commented on August 24, 2024

@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.

Peanuuutz avatar Peanuuutz commented on August 24, 2024

@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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.