How many components do you want?
# language-proposals
k
How many components do you want?
t
Let’s discuss in thread. 1) It could be an arbitrary chosen number, like number of FunctionN interfaces (22) seems fine for any needs I can imagine (Anyway, anyone making a data class with more than 22 fields is a mad person for me) 2) I want just the generic
Component1<A>
,
Component2<A, B> : Component1<A>
etc.
k
Hmm I recall there being some change about this a couple months ago for the function interfaces, can't remember what exactly though.
d
So the thing you suggest is the following:
Copy code
interface HasComponent1<out A> {
    fun component1(): A
}
Copy code
interface HasComponent2<out A, out B> : HasComponent1<A> {
    fun component2(): B
}
... etc, And these would be implemented by data classes implicitly? I used the interface naming of
HasComponent
as you can see, but it doesn't really matter either way.
t
@Dico exactly.
d
Actually, if there's inheritance between the interfaces, that might allow destructuring of data classes when declaring less variables than properties of the data class 🤔
k
The problem is that giving meaning to position of properties is already leaning a bit on the "too implicit" side, but it's not that bad because it always happens very locally. If you make interfaces out of this behaviour you risk it spreading.
I would have liked the
componentN
behaviour to be opt-in for data classes, like for
data class Point(x: Int, y: Int)
it makes sense but less so for
Person(adress: Adress, age: Int)
. But that's too late anyway.
t
@Dico Kotlin supports contravariance, so that is not a big deal
d
@themishkun sorry I do not understand how contravariance is relevant. I'm just saying it might be better to declare all of the properties and drop the inheritance to avoid the ability to destructure an instance using less variables than it has properties. This is because the compiler might check if it's an instance of
HasComponent2
for example to check if it's allowed with 2 variables, when it can also be a
HasComponent3
or
HasComponent4
. It would be weird. Anyway, it's not important. Just something small to consider for how the interfaces are implemented.
t
@Dico I now Understand your concern. Thanks. It will also secure the performance removing the need of bridge-methods
k
@Dico That is already how it works right now though:
val (a, b) = list(1, 2, 3)
@themishkun What are those bridge methods?
d
Hmm, good point @karelpeeters It's allowed for data classes as well, as I just tested:
Copy code
data class Data(val a: Int, val b: Int, val c: Int)

fun test(data: Data) {
    val (a, b) = data // no error
}
Well, I guess the inheritance only simplifies implementation then.
k
Yeah both
List
and `data class`es have
componentN
function, no difference as far as the compiler is concerned.
d
I see. It seems that for
List
,
componentN
function is defined up to
component5
. Maybe a little low for an upper limit for the rest of
HasComponent
types.
k
@themishkun I know about them, but aren't those only for input parameters? As far as I know there's no issue with returning more specific types, which is the only relevant part for the
componentN
functions.
d
ah,
componentN
functions are operators. I didn't know that.
k
To be honest you should really stop destructuring after 5 values anyway.
a
You can do it already, so the only thing that this KEEP would add is auto-implement those interfaces on data classes?
d
And add the interfaces to the standard library.
I guess.
t
@Andreas Sinz yes, and implement some library functions to use that advantage
k
For example?
t
Also, arity for 1.3 will be changing to 255, so that will be fun. component237() just strikes me as confusing. I kind of like the "If you are using more than component5(), find a different way" mentality.
k
Yes! That's what I was referring to earlier: https://github.com/Kotlin/KEEP/blob/master/proposals/functional-types-with-big-arity-on-jvm.md, thanks for reminding me.
👍 1
d
I agree that there should be a sensible limit.
t
@todd.ginsberg I totally agree that there should be a far more limited ammount of
ComponentN
-s, I just referred to FunctionN current limit as some point to start evaluating. How did you choose this number when implementing FunctionN?
t
I don't have an objective way to decide on a number, if that's what you're getting at.
k
@themishkun What kind of use cases do you have in mind? Earlier on you said stdlib should add some functions using it, which ones?
t
@karelpeeters I mentioned some of them earlier. For example, a neat little function
Copy code
inline fun <A,B,C> Component2<A,B>.consumeBy(f: ((A,B) -> C)): C = 
f(this.component1(), this.component2())
the usage is like with the spread operator for arrays:
Copy code
fun drawAt(x: Int, y: Int)
Point(1,2).consumeBy(::drawAt)
d
So is that essentially destructuring for function parameters?
t
another one would be a pattern-matching, used like
Copy code
when (user) {
     in User("johnDoe", any()) -> ...
     in User(any(), 13) -> ...
     else -> ....
}
You could think of that as Mockito matchers, if you never met pattern-matching before
it becomes more interesting when combined with sealed-classes
k
Hmm how does that work? You want
in
to check each of the components, if
contains
isn't defined? And what does
any()
return here? Why are there
User
objects here?
t
this is the abstract example, the more realistic one would be
Copy code
in (::User).pattern("johnDoe", any())
or any other syntactic trick that will make a
Pattern
object and give it some matchers. I can create a gist for you
@karelpeeters here is the gist with working typesafe no-reflection pattern-matching with ComponentN interface example https://gist.github.com/Mishkun/9430a6740858876895c1eb0f870b7a59
k
That does look promising indeed, although it's missing being able to capture those
any()
matches in a variable.
d
well, any() can return an object whose
equals
always returns true. However, that would violate equals contract, and it would have to be the LHS of the comparison. It should probably be a contextual keyword of some sort instead. As for creating a
Pattern
object, I guess that would be cached statically for when expressions? It might be better to just inline the comparisons. Finally, what about:
in User(name = "mikhail")
This wouldn't be possible with the
componentN
functions however, but it would make sense semantically.
t
@karelpeeters unfortunately, i think there is no chance to really destructure things and bind variable to them without some syntactic shugar =(
v
@themishkun one of the cons of inhereting data classes from generic interfaces would be boxing of primitives. Currently componentN functions do not box, but in your implementation they will
@themishkun another argument against this proposal is an idealogical one: in Kotlin accesing components by names is preferable to accessing components by their positions. The reason is that positional access makes automatic refactoring much harder. Destructuring itself kinda goes against that principal, so I think they want to keep it limited and underpowered.
Make sure to mention both if those concerns in the KEEP