-
Notifications
You must be signed in to change notification settings - Fork 361
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Companion values #106
Companion values #106
Conversation
First draft of proposal
Two different versions of aggregation syntax
protected companion val: ILogger = LoggerFactory.getLogger(A::class.qualifiedName) | ||
|
||
fun foo(){ | ||
info("Log entry") //the same as [email protected] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just logger.info()
? To save a few characters?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
companion object
also helps to save few characters as well as apply
and run
functions from stdlib. The same story here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But one big difference between this feature and apply/run/let/etc that scope functions do not require any additional language features, all of them are just library functions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, but companion object is a language feature with similar behavior.
### Constructor parameter | ||
Ability to aggregate object passed as constructor parameter: | ||
```kotlin | ||
class Derived(private companion val b: Base1): Base2(a), Serializable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually looks somehow related to typeclasses proposal that also tries to provide ad-hoc polymorphism.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about typeclasses. This declaration has the same behavior as declaration of companion value at class level.
class Derived(private companion val b: Base1){ }
is equal to
class Derived(b: Base1){
private companion val: Base1 = b
}
Maybe you should point out that |
When a
Another syntax would be to not mix the visibility of the companion object with the visibility of the field, for example
|
Hey guys, I find this proposal very interesting, as it mixes (the less controversial) aspects from KEEP-87 (type classes) with aspects of KEEP-176 (compound extensions). I think possibilities of companion vals are still described a bit sparsely here, but I did a prototype implementation of my vision of this feature in https://github.com/hannespernpeintner/kotlin/tree/keep-106 . Take a look at the readme and the test cases I added. From my point of view, the companion keyword could be used intuitively at any scope, that's why I enabled it at every (?) scope level (see tests at https://github.com/hannespernpeintner/kotlin/tree/keep-106/compiler/testData/codegen/box/companionval). Some disclaimers:
|
Hey, it's me again. Since I wasn't able to implement above mentioned bit in the compiler itself, I did a quick try to implement an annotation processor, that generates accessors for members of companion properties of a class. Works somewhat well, maybe take a look at the repository if someone is interested. |
I like the idea but I don't like the usage of keyword "companion" for this. The proposal adds a feature to classes that already exists for functions: For functions this feature is called "extension function". Indeed, with this proposal every function in the class implicitly becomes an extension function, whose extension receiver is implicitly provided. Hence I would propose to rename this proposal "extension values" and use a new "extension" (or extend) keyword: extension val a: Context = ... All functions in a class that has such property would implicitly become an extension function whose receiver would be implicitly set to a. Disclaimer: The JVM language "Xtend" has a very similar feature and they also use a similar keyword for this (extend or extension). |
I think you slightly missed the point here :). The extension function feature makes a function capable of being invoked on a receiver which happens at declaration site. Companion vals do two other things. First, they are made available as a receiver at the use site. Thus not making functions of the surrounding class extension functions, but the other way around: making extension functions of the companion val appear as if they were regular functions. The other thing is that public companion vals of a class export the val's members, so that other use sites can use them on the surrounding instance rather than on its member. I think this is more similar to what companion objects do, hence the name. Extension providers in extend do a slightly other thing, because they indeed make extension functions out of regular functions. |
Your description does not match the proposal, which reads: "companion keyword can be applied to val or var declarations inside of class declaration or at constructor parameter level [i.e. on a property P inside of class X]. This keyword indicates that all members of such value [P] will be accessible implicitly inside [members of] class [X]" In other words: the functions of class X implicitly become extension functions that take P as a receiver. Therefore members of P become available inside those functions. The receiver P does not need to be provided explicitly, it is provided implicitly. I did not give much thought to the public property case, as that one is not very well described in the proposal. |
To be a bit more explicit, this is the example of the proposal rewritten to use extension functions:
This already works today, and it uses extension functions. The proposal basically defines syntactic sugar for it, by making the functions implicitly receive the receiver. |
Yes, you are right, my description doesn't match the proposal, because as in #106 (comment) mentioned, i don't see a reason to artificially limit the companion keyword location, as opening up allows for use cases that are definitly demanded by people (compound receiver). I wanted to put this to discussion and added example tests for use cases. Your example However is not correct i think. Turning sth into an extension function doesn't give us anything, because it would be now the caller's job to provide the receiver, which is clearly not the case in the original example. That's why i said the difference is use site vs declaration site. In your example, baz() is not invoked on base1, but on an arbitrary Base1 instance the user puts into scope before using Base1.foo() |
I repeated multiple times that the receiver is provided implicitly, removing the burden of providing it explicitly at the call site. I am not saying that the implementation must follow this description. But conceptually this is what happens here. I understand that you allow this feature at smaller scopes in your prototype. That's cool. But my interpretation of the feature still holds conceptually. Just at smaller scopes. |
Than i must admit that i don't get it, maybe it's my language barrier, I'm sorry :) Could you take your last example and explain how the receiver of type Base1 should be brought into scope, other than the user does sth like with(...){}?? I find this very confusing.. i think a better comparison would be with Not making things extension functions, because functions could already be extension functions, but with inserting with Statements everywhere in the class surrounding the companion... This would be equivalent:
Which is exactly how it is implemented :) As for the "Exports" feature (aspect two in this discussion), does the user really need to know about the implementation Detail (delegation) how Derived implements the method baz() ? |
I agree with your idea of inserting implicit with statements. I cannot tell whether that is the best solution but it seems a reasonable enough idea. I did not mean to introduce real extension functions. I just meant that the proposal seems conceptually very similar to implicit extension functions, especially if we had compound receiver extension functions. I find the similarity to extension functions greater than the similarity to companion objects. I also might have used the term "receiver" incorrectly, whereas in fact I meant "context". I still don't think that this proposal could replace compound extensions. See my comment in the other keep for a use case that needs both proposals (#176 (comment)). |
Hm, i think it indeed can. Take a look at https://github.com/hannespernpeintner/kotlin/blob/keep-106/compiler/testData/codegen/box/companionval/CompanionAsFunctionArg.kt Which could easily be extended to a fun that takes two or more companion params and make them available in the body. If i weren't to bad to implement it, i would have made a better implementation example with lambdas :) |
But that would greatly increase boilerplate at the call site, because I would need to provide the arguments explicitly. The whole purpose of the extension function is simplifying the call site. I don't need to provide the receiver because it is already available in call site's context (see how homeLink() function is invoked in my example). |
Uhh, now i got it. We're back at scala implicits completely i fear. It was Not my intent to make parameters other then the single true receiver "Passed" in implicitly. That's also what keep-87 avoids in order to be not unmaintainable :) companions are used as implicits receiver at the use site, Not as implicit parameters at the call Site. What you call boilerplate, i call proper code, because i don't want parameters to be passed in (besides type classes but that's a different story that avoids arbitrary scopes). Your example only works because your scope and your receiver are the same context and your receiver implements proper interfaces. In my example aren't any interfaces. Edit: and the whole thing about extension functions is not to make the receiver parameter on the call site invisible, but instead visible as a receiver. Companion vals are made visible as a receiver context, because they are companion params in a function for example, Not a receiver parameter, as in compound receiver proposal. |
My example forces me to expose public properties, whereas I normally would like to make them private. I would not call that proper. Yes, the encapsulation could be fixed in plain Kotlin, but it would involve even more boilerplate code. It could be much simpler, cleaner and more flexible by combining the proposal in this keep with the compound extension proposal. Disclaimer: I don't like Scala's implicit arguments that much. But the reason for me is that it would be more idiomatic in Kotlin to use compound extensions instead. They would work very well together with this keep's proposal in an idiomatic Kotlin way. |
I now updated my example to take advantage of companion values together with compound extensions: #176 (comment) In my opinion the benefit of combining both proposals is very clear. |
I believe that this keep together with compound extension functions ( #176) could support many of the use cases that are given to motivate type classes ( #87). I added an example that illustrates this in that keep here and here. The classical monoid example would go like this, when using companion values: interface IMonoid<A> {
fun zero(): A
fun A.append(other: A): A
}
class StringMonoid : IMonoid<String> {
override fun zero() = ""
override fun String.append(other: String) = this + other
}
fun <A> IMonoid<A>.doMonoidStuff(a: A) {
...
}
fun main() {
// the companion val can go into different scopes.
// in this example we are scoping it locally in this function:
companion val stringMonoid = StringMonoid()
val someString = "hi world"
doMonoidStuff(someString)
} |
For comparison, there exists a similar feature in the language Xtend. It is called "extension provider" there. It also supports extension imports. https://www.eclipse.org/xtend/documentation/202_xtend_classes_members.html#extension-provider |
Propose syntax for implicit access to members of the value declared as companion value.