I say let’s get rid of the idea of multiple receiv...
# language-proposals
m
I say let’s get rid of the idea of multiple receivers 😄 https://youtrack.jetbrains.com/issue/KT-42435#focus=Comments-27-4466122.0-0
👀 2
r
The last part mentioning named args is what we have in meta now with @Given . I personally prefer multiple receivers but the argument of syntax explosion is the same we have now with generic constrains. Inside the body you get the intersection of all types involved on the bound.
m
I had linked to the wrong comment, sorry 🙂 Please check the latest one.
It’s about symbol explosion, not syntax issues.
l
@Marc Knaup I think you can edit your message to put the right link 😉
m
I did already 🙂
👌 1
r
Symbol explosion is imo a good trade-off over having to call run multiple times and it's as explosive as you want to make it. I see limiting receivers the same as limiting args. Should be up to the user. Receivers and receiver dispatchers are ultimately args and it's the user choosing when to nest members or increase arg count.
m
What do you mean by calling run? Kotlin supports explicit parameters and implicit receivers. What I’m proposing is implicit parameters for many potential use cases because having using receivers instead is totally unnecessary. It leads to so many symbols in scope that you just don’t need.
r
The reason why you would want extra receivers is to bring their members in scope. Without this new feature we need to call arg.run{ a.foo() } where foo is a ext member of arg
So if you have say 3 implicit args you end up with 3 nested calls to .run
m
No, why? For implicit arguments you don’t need that nesting.
implicit (a, b, c) { … }
The question is what is the use case for having three values/types not just in scope but also all their symbols.
r
But that assumes implicit is a special function that can't be implemented today in Kotlin and it'd would project the intersection of potentially non interfaces over it's receiver right?
m
It is a special Kotlin function, yes. But it doesn’t need any intersection type. it doesn’t intersect anything. It merely makes certain values available implicitly by type, for example as implicit function parameters.
r
You need multiple receivers to implement that because the scope lambda has to intersect a, b and c?
m
Nope, no intersection
You cannot access it as a type. You can only individually access
a
,
b
and
c
, each with their respective type
r
The Kotlin compiler internals calls that the intersection type of the bounds which is how it brings all members of all types involved into scope.
Didn't mean the user
m
The Kotlin compiler only needs to remember that
a
,
b
and
c
are implicitly in scope. It doesn’t need an intersection type because their symbols are not accessible anyway.
r
Yeah having such synthetic function can be an option but that and the implicit annotation is effectively implicits like in Scala. Is resolution scoped or global?
m
To illustrate with a cheap example:
Copy code
fun foo(@Implicit a: A, @Implicit b: B, @Implicit c: C) 
implicit(a, b, c) {
   foo()
}
Compiler:
Copy code
// implicit(a, b, c) {
context[A::class] = a
context[B::class] = b
context[C::class] = c

// foo()
foo(context[A::class], context[B::class], context[C::class])

// }
delete context[A::class]
delete context[B::class]
delete context[C::class]
r
The Kotlin compiler has intersection types not exposed to the user but it's how the intersection of the types provides the members in body resolution. Didn't mean to have this a user feature. Just what they call it
If you are marking as @implicit why do you need the function again?
m
Not sure what you mean?
r
You have a function that takes implicit args but activating their scope is explicit. Is that by design?
m
No, that’s what would happen internally
by the compiler
r
I see
m
I’ve annotated the code to make it clearer
r
What I understood from @elizarov proposal in his talk is that he was after a more idiomatic solution for this that would not be modeled as implicits args. This is fairly similar or the same as the first prototype of KEEP-87 and what we have in meta now prototyped as given.
Thanks I'll check it out again!
m
(the code here in chat 😄)
r
I'd like implicits like this too but something people complained frequently in Scala was the opposite case. They were forced to name implicit args when all they care about those where the functions they expose. Eventually implicits got broken down in dotty in specialized features for those use cases. Most of the time you want an implicit arg you never care about its name. Given use sites apply implictly too and you don't expect users to name those unless they are providing an explicit arg.
Implicit named args also replace the DI case but make you decide how the compiler it's going to provide those values. Do we obtain those at use sites from the scope or which is the way to provide them. Are providers limited to inline functions or would it support object, val and class?
m
What were the use cases where Scala users need an implicit but nameless parameter? How would they use it? For that we already have extension functions and properties.
Right, I didn’t think about DI. Could be a good use case. What do you mean by “Are providers limited to inline functions or would it support object, val and class?”
The problem with DI would be that we can only distinguish by type. You cannot tag them or anything like that.
At least you can pass the DI container around.
r
The use case in scala is type classes. The ability to provide a family of extension functions over a given type that can be used in functions that are polymorphic where in the function you can say a given type is constrained. Same we have in Kotlin with 'where' but not just for subtypes. In Kotlin it applies the same, you can constrain a A : Comparable<A> and you don't need to name the comparable part.
m
Ah okay. Is my suggestion related to that? Mine is about providing additional parameters, not constraining types.
r
Regarding val, object, fun and class I ment that in order to provide an implicit value we need a declaration that provides it. val, object and inline fun support singleton construction where the compiler does not need to instantiate. class an fun support prototype style or híbrid modes but the compiler desugars to new instances or calls to the function. https://github.com/arrow-kt/arrow-meta/blob/master/compiler-plugin/src/test/kotlin/arrow/meta/plugins/proofs/ResolutionTests.kt#L70
Providing additional parameters is related to type constraining because it's the same for multiple receivers or args. Either the compiler provided the instance or the user is responsible to ensamble it. With multiple receivers you can defer construction to the edge as long as you are in the scope of the receivers. Implicit arguments that are named need to be implictly injected from a construction strategy since call sites may not be in the receiver scope.
Both are valid I think it's a matter of what kind of style we'd see rather promoted
m
So with my
implict(a, b, c) { … }
example you could still use
given<A>()
, can’t you? No need for a name.
r
And both cases can use implicit type or named injection since the decorator proposal is also named
m
It’s the same way the function pulls implicit arguments
@Implicit a: A
is basically a local
val a = given<A>()
Just that you can override when calling the function.
r
Yes you can always summon the implicit identity which is that function if that function is synthetic , that function is currently defined differently and it's just a fun version of that property you just posted
That function promotes a bad pattern though. Leaves your users unable to override explicitly since you can use it on bodies of functions with no arguments
That function works great for overrides if you have scoped implicits like Scala since you can still override in the scope with additional implicits but won't help you if implicits are top level or coherent like subtyping in Kotlin is. You will give no chance to override. The compiler will inline the implicit in the body.
m
Still can’t wrap my head around it 😅 If you think the two problems are closely related please give feedback in YT how the approaches could benefit/influence each other.
r
I guess what I wanted to share is just that we have explored this side too. Roman's proposal already supports providers as inline decorators. That is a form of DI since those values for those types would need to be injected. Kotlin already has implicit single receivers. If the receiver is the implicit part we should keep it there as it is always good practice to not mix implicit and explicit args to not confuse users. But it's just some thoughts based on experience. I like what you are proposing but I still prefer to solve the multiple receivers first since Kotlin already supports implicit receivers and the idioms are known. I too went proposing implicit args before and I have changed my mind so I thought I'd share.
m
Yeah. I just see how receivers even today rapidly make code difficult to understand because it gets very difficult to follow what symbol resolves to what receiver. I dont mind using receivers, as long you can name them and keep their symbols out of scope.
h
Interesting discussion you two, i am always amazed how all discussions result in effectively scala implicits, sth that most people decided they don't want xD I think an important thing to realize is that symbol explosion is exactly what we want, and i know how that sounds. Yes, can be abused, as every single other language feature as Well. But bringing contextual things into scope is exactly what typeclasses do and what we need for dsls. I think there is @DslMarker and maybe we need more mechanisms to limit symbols in a very specific scope, idk. A language feature powerful and flexible enough can solve multiple problems at once, done right we have better di, typeclasses and better dsls at once. I think a notion of multiple receivers is really what kotlin needs. First class. Maybe it would be nice to be able to name receivers optionally.. with multi receivers it can be sth like receiver@Foo and receiver@Bar instead of this@functionName
m
Why would I want all those symbols in scope? Even if I would, I can use
with
already 🤔
h
Because for example a User shouldnt need to care how extensions are brought into scope. When er have a coroutine scope and an extension provider, passing a lambda into this combined scope should Not be worse than without having a coroutine scope for example. But it currently is
I also think there is a lot of valuable info and examples already in three keep discussions that touch those things
m
The coroutine scope is a special wrapper around a context made to keep symbols out of the scope. It's more of a hack than a good example.
There are so many good examples and i actually dont think that coroutinescope is a bad example by any means. It's the best example how contextual things would benefit, besides the chance people do bad coding and let symbols explode
I don't think we are at the point anymore to question the usefulnes of some sort of multi receivers. Working several years with kotlin just showed that it would be helpful, in the same way receiverszhave been
I also think multi receivers would help (!) Reducing confusion about where a receiver or symbol comes from, because there is no nesting anymore and we can finally extract things into functions that needed to be nested lambdas because of missing multiple receivers