Are normal tuples coming to kotlin? standard libra...
# getting-started
u
Are normal tuples coming to kotlin? standard library does provide
Pair
and
Triple
but even with the only 2 cases, the
to
syntax sugar works only for pairs, which is ughh?
s
If you need an easy syntax to create triples, you could make your own infix function to do so. See the simple implementation for
to
u
infix doesnt work for 3 params, doesnt it?
s
No, you would need to write a new infix that converts a pair into a triple, given another argument
u
that seems like a unnecessary allocation
s
but as you extend from
Pair
into
Triple
and more complicated data structures, you should instead look into using a
data class
instead
💯 1
u
ragardless, I'd still like the standard
(a, b, c)
syntax, that scales to abitrary num of params
same 1
h
If they're all the same type (or if you don't care about the type), maybe you can use lists? You can use destructuring for lists:
Copy code
val tuple = listOf(2, "tuple", 2, "furious")
val (a, b, c, d) = tuple
If you care about types (or want to use things like
copy()
), I think data classes are the way to go:
Copy code
data class Tuple4<out A, out B, out C, out D>(val first: A, val second: B, val third: C, val fourth: D)

val a = Tuple4(2, "tuple", 2, "furious")
val (a1, a2, a3, a4) = a

println("${a1 + a3} ${a2 + a4}") // "4 tuplefurious"
See this SO answer for an example of creating some general purpose tuples with infix notation. I believe I read some discussion/reasoning behind the decision for kotlin to stop at 3 (
Triple
) but I can't find it. I thought the general gist was that already for 2 and 3 it was preferred to use dedicated data classes (over
Pair
and
Triple
) to communicate intent/meaning better. E.g. Kotlin has
Map.Entry
and
IndexedValue
where maybe a
Pair
would also have sufficed.
j
I'd be curious to know why you need tuples for, over data classes? I find that pairs and triples are exceedingly annoying because then you see
first
and
second
and never know what it means. With data classes you can put semantics in the names of the properties, which is far superior. And it is extremely cheap to declare and use a data class
u
because I have to go and define and name the data class, which is annoying for a private function etc. tuples and data classes are not exclusive
👍 1
j
The naming hassle is precisely what you save readers from (you can convey the idea using the name instead of making them figure it out). Using tuples moves the work from the writer to the reader, which is usually a bad thing
u
private apis have different level of requirements for this imo
I dont use tuples for public apis
j
Slightly different, but still the same principle applies for the maintainers of the code
u
not really
Copy code
val (title, subtitle) = when (whatever) {
	FOO -> ("foo", "foofoo")
	BAR -> ("bar", "barbar")
}
this is absolutely good code and tuples make it better
j
Why aren't those properties on
FOO
and
BAR
?
u
Copy code
val (title, subtitle) = when (whatever) {
	FOO -> TitleAndSubtitle("foo", "foofoo")
	BAR -> TitleAndSubtitle("bar", "barbar")
}

data class TitleAndSubtitle(val title: String, val subtitle: String)
this is too noisy
s
I'd argue that your example would start to break down after more than two arguments. Title/Subtitle is easy for the programmer to keep in their head, but what if you wanted
(title, subtitle, author, description)
? At this point, your signature becomes
<String, String, String, String>
and requires the context from a different space to make any sense. Was it author, then description? Or description then author?
u
there is no signature, its a private detail
say ui mapping stuff
j
The enum could directly have these properties without the need for a
when
at all
s
when one of these fields ends up with the type
Any
, its going to be hell figuring out where in the
when()
block you've got your types wrong
u
why would you encode UI detail on a enum unless you mean extension, which is way more code than even the data class & a needless abstraction
I dont buy the type argument, that's like saying you cannot have 2 local variables of same type in a function, because you could mix them up
for local variables this is absolutely fine
the whole idea of tuples is inline adhoc stuff, expressiveness
s
function arguments have names though, while tuples only have their position
There's a loss of information there
u
are they less safe? yes is that okay? for local vars yes
j
why would you encode UI detail on a enum
It depends on the enum, but in this specific case it looks like it should be there, because
title
and
subtitle
seems conceptually properties of your enum values, so why not declare them with the enum values? It would be easier to reason with a case that's a bit more real, though, I grant you. If the enum is rather unrelated to the tuple, then I'd argue it's even better to have a proper data class with a name that tells what the tuple represents. Then you would obviously not call this data class
TitleAndSubtitle
, but the name of the thing that has a
title
and a
subtitle
u
I want basically a nicer way of saying this
Copy code
val title: String
val subtitle: String
when (whatever) {
	FOO -> {
		title = "foo"
		subtitle = "foofoo"
	}
	BAR -> {
		title = "bar"
		subtitle = "barbar"
	}
}
this is like my 99% usecase
j
And I'd argue that 99% of these cases actually should be rewritten either by adding the properties on the enum, or by mapping to a proper type
u
but I just wanna tie 2 stuff together, I dont want to think of proper types, its not a public api, thats why Pair exists do you define data classes for map arguments?
I dont really see why this should be on the enum, other than an extension
j
but I just wanna tie 2 stuff together, I dont want to think of proper types
To me this piece of code is as annoying as tuples. Proper types are much easier Extensions could definitely help with readability too, if you can't touch your enum, but most likely the enum has semantics that you could use here.
u
regardless; defining & naming a new referential type that has 1 callsite, is overengineering
j
Do you also never extract a function if it has only one caller?
u
thats my point, you extract at your own discresion when it makes sense, forcing every tuple to named data class is like forcing every line to be a separate function (which forces a name)
you can just use a data class and destructure the components if you'd like
j
> forcing every tuple to named data class is like forcing every line to be a separate function (which forces a name) I get your point and disagree. Because it's about forcing groups of values to have a named type, not every value. So the analogy would be forcing groups of related statements to be extracted to methods, not single lines (and oh, boy, I wish we could have that!).
u
I never got why is this is so controversial. This is 1:1 analogy with lambdas. Also, other languages have it and people live. You can abuse any api
👍 1
s
I know this is probably an at least slightly contrived example, but I'll second the notion that you may be using the wrong abstraction for the problem. Why do you think the title/subtitle doesn't belong on enum instances? It's static data either way—why does it matter if it lives inline in a
when
or grouped together in a
Book
enum (or set of
object
declarations or something similar)
u
its hard to answer because this is just an illustration but generally, its a question of scoping, does every enum callsite need to know about it etc.
but im sure you as well wouldnt make the enum be an union of everycallsite, so yea, dont focus on that
j
It's not really a question of scope, but rather semantics. It doesn't matter who needs the information if the information belongs with the enum. If you have a
Book
enum with some entries representing the books, you definitely don't want the
title
,
subtitle
, or
description
to be declared in a
when
somewhere else even if this is the only usage of it. The title definition belongs with the book instance definition because it's information about the book itself.
👆 1
So in the example above, it really depends on whether `title`/`subtitle` are semantically linked to
FOO
and
BAR
or not
If they are not related, then I would definitely prefer an extension
fun TheEnum.toMyOtherThing(): MyOtherThing = when (this) { ... }
mapping the instances (or a static map somewhere even), to convey the mapping to the reader and not mix it with some other business logic
v
Wow that's a lot of pushback on general tuples! 😅 I wonder if the negative feedback comes from people experienced with languages that do have general tuples, like Rust or Scala. I think there are certainly good uses for tuples that are simply cumbersome to express without them, such as in library abstractions that need to group very generic values (of parametric types) for which there is no good name. The Rust and Scala (standard) libraries make generous use of tuples.
That said, I think Kotlin's pattern-matching is in a very sorry state that's likely never getting out of the semantics dead-end of
componentN
methods. This severely limits the use cases for tuples in Kotlin, since you can only pattern-match one level deep and in very limited ways (although pattern guards are hopefully coming). The fact that tuples are costly by default on the JVM probably doesn't help.