Hello, World! A generics question that lets me pe...
# announcements
b
Hello, World! A generics question that lets me perplexed ๐Ÿค”!
Copy code
fun <T> foo(a: T, block: () -> T) {}
foo("Hello") { 1 }
How come this compiles?
T
is
String
for
a
but
Int
for the
block
lambda. Bonus question: what's a way to make this not compile and enforce the same type? ๐Ÿ™‚ Thanks ๐Ÿ™
d
T
is inferred to be
Any
, which is the common supertype of
String
and
Int
,
foo
then returns
Any
.
๐Ÿ˜ฎ 1
โž• 1
As for the bonus question: I don't think you can.
b
๐Ÿ˜ข
n
yeah, Kotlin tends to infer Any sometimes in situations where you would really want a compiler error, but there's no magic way for the compiler to understand I guess those particular cases
Another example is passing totally unrelated types to
listOf
v
The compiler will then fail when you try to use the result somewhere
n
when you eventually hit some non-generic, or suitably constrained code, yes ๐Ÿ™‚ Not the end of the world but it can mean your compiler error is farther from the written error
v
Well, that's a PoV whether it is a written error or not. It could be perfectly fine and intended to have
Any
there. :-)
n
Indeed, but it's the downside of having a top type like that. Not that there aren't benefits as well of course.
f
Copy code
fun <T> foo(a: T, block: () -> T) where T : String {}
will constrain it
m
๐Ÿคซ You didnโ€™t hear that from me.
Copy code
import kotlin.internal.*

@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
fun <T> foo(a: @Exact T, block: () -> T) {}

fun main() {
    foo("Hello") { 2 }
}
(evil hackโ„ข) Alternative with warning, but no less evil:
Copy code
import kotlin.internal.*

@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
fun <@OnlyInputTypes T> foo(a: T, block: () -> T) {}

fun main() {
    foo("Hello") { 2 }
}
๐Ÿค 3
b
To me, it seems like the original code is kinda an oxymoron. By using
T
you're explicitly saying you don't care about the type. So it's kinda contradictory to then turn around and be surprised that any type will work.
v
@flosch that would miss the point of generics, you couldn't do
foo(1) { 2 }
and as
String
is final it would be pointless to use generics at all
โž• 1
f
well that depends on what the use case for the function is. there might be a reason to only constrain it to string or inheritors of string. also the bonus question was
Bonus question: whatโ€™s a way to make this not compile and enforce the same type?
and this is how you do that
v
Oh
String
is not
final
in Kotlin, missed that. Assumed it is
final
like in Java. But you still miss the meaning of the bonus question. The question is how to enforce both `T`s are of the same non-
Any
type, but without restraining to just
String
subtypes. As I said, the goal is that
foo("Hello") { "World" }
works as well as
foo(1) { 2 }
or any other type, while
foo("Hello") { 1 }
does not and there seems to be no way without using internals to reach that.
d
String
is final in kotlin, because it is not
open
๐Ÿ˜‰
m
String
is definitely
final
๐Ÿ˜…
v
Ok, thank god, so my statement was correct, using generics then would be absolutely pointless
fun foo(a: String, block: () -> String) {}
f
ok true string was a bad example, any open class then for my example I get the question now ๐Ÿ‘
b
Thanks a lot for all the reactions. I am very surprised to see this (and to see this only now after like ... 3 or 4 years of "kotlining" professionally! but that's another story). I was thinking maybe this is due to the lambda, but now I see that this simpler example also has this issue, and now I'm even more surprised:
Copy code
fun <T> bar(a: T, b: T) {}
bar("Hello", 1)
v
Why? The inferred common supertype is
Any
still. As you don't do anything with
a
or
b
where
Any
cannot be used, this is just fine.
b
no you're right I guess I shouldn't be surprised that it compiles, but still surprised that there's no way to do this?
(not including the
kotlin.internal
ways ๐Ÿ˜›)
n
it's an inevitable result of a) having a top type, and b) allowing type deduction to work with inheritance
C++ has neither of those things, and that example would not compile
(I should say, generic type deduction)
without b) you also wouldn't be able to do
bar(listOf(1,2,3), mutableListOf(4,5,6))
a) is probably the more controversial one but I do think
Any
is useful
b
Thanks for the insight! At the very least, I'm glad because TIL ๐Ÿ™‚
n
i was coming for C++ and it was very very weird for me at first
*from
but you get used to it, and as bjorn pointed out, it usually just means you get a compiler error on the next line instead of immediately
v
I'm not called bjorn ๐Ÿ˜ž It is bjรถrn or bjoern ๐Ÿ˜‰
n
i don't umlaut ๐Ÿ™‚
but I will try to add the e
๐Ÿ‘Œ 1
if I remember
is that a general thing in german, that o with umlaut -> oe ?
v
Yes
n
gotcha, thanks
v
also รค -> ae, รผ -> ue and รŸ -> ss
n
the strauss one I knew, I think
I was in germany and had to ask about how to pronunce the "beta" ๐Ÿ™‚
v
lol
b
Here's a trick that might work if you only need to use basic (Comparable) types:
Copy code
fun <T: Comparable<T>> foo(a: T, block: () -> T) {}
At least it will cause a compiler error.
b
Interesting ๐Ÿค” Let me try that.
v
Would of course still work with a type and its subtype
โ˜๏ธ 1
But
Any
wouldn't be inferred, as it is not
Comparable
b
indeed
amazing
v
What we miss is
Copy code
fun <T> foo(t: T) where T : !Any {}
๐Ÿ˜„
๐Ÿ‘ 3