is this behavior expected of reified parameters?  ...
# getting-started
a
is this behavior expected of reified parameters?  Given (this is as simple as I could drive down my example):
Copy code
inline fun <reified R, reified S> reifiedGenericIsSame(r: R, s: S): Boolean = (r is S) && (s is R)
interface FooBar<out A: Any, out B: Any>
typealias FBOI<A> = FooBar<Int,A>
typealias FBOS<A> = FooBar<String,A>
and the following:
Copy code
open class Base<out A: Any, out B: Any>(val a: A, val b: B): FooBar<A, B> 

class SpecialOfInt<out B: Any>(b: B) : Base<Int, B>(1, b)
class SpecialOfStr<out B: Any>(b: B) : Base<String, B>("1", b)

// val soitd: FBOI<Int> = SpecialOfInt(2)
// val sostd: FBOS<Int> = SpecialOfStr(2)
val soi = SpecialOfInt(2)
val sos = SpecialOfStr(2)
//
// EDIT
//
val soitd: FBOI<Int> = soi
val sostd: FBOS<Int> = sos
then this holds:
Copy code
reifiedGenericIsSame(soi, sos) shouldBe false     // passes
reifiedGenericIsSame(soi, sos) shouldBe true      // FAILS
reifiedGenericIsSame(soitd, sostd) shouldBe false // FAILS
reifiedGenericIsSame(soitd, sostd) shouldBe true  // passes
which 🤞is VERY counterintuitive, as it appears a typealias (allegedly syntax sugar) is changing a result semantically. I suppose type erasure side effects may be invoked as the mechanics, but this question is about the intrinsic behavior. Could please anyone provide me with great kotlin wisdom?
m
The
is
operator doesn't take into account the generics of the type being checked so
listOf("foo") is List<Int>
will be true. That's why you get
true
for
soitd
and
sostd
. On the other hand
soi
and
sos
are different types, so the
is
check fails as expected.
The type alias is not changing anything. Both
soi
and
sos
are
FooBar
so the
is
check passes. It doesn't mater that one is
FooBar<Int,A>
and the other
FooBar<String, A>
a
Hm. Am I misreading your reply?
reifiedGenericIsSame(soi, sos)
is
false
They are different types, conforming to
FooBar
. Also
soitd
and
sosts
are different types. If I set
Copy code
val soitd: FBOI<Int> = soi
val sostd: FBOS<Int> = sos
the outcome is identical.
m
I do think I got confused.
soi
and
sos
are different types so the method correct returns false.
soitd
and
sostd
are also different types, but are declared as the same type
FooBar
. Since the
typealias
is just syntax sugar, both
R
and
S
in
reifiedGenericIsSame
are both
FooBar
. Bot
soitd
and
sostd
are of types that are subtypes of
FooBar
so the function returns true
The types of
S
and
R
are determined at compile time and not runtime.
a
I hear you. The type of
soi
as well as the type of
soitd
IIUC is
SpecialOfInt
, i.e.
Foobar<Int, A>
. The type of
sos
and
sostd
is
SpecialOfStr
, i.e.
FooBar<String,A>
. The difference between
soi
and
sos
is correctly resolved. The difference between
soitd
and
sostd
is not. The only delta is the typedef
FBOI
or
FBOS
. Nothing else is different. I have difficulty reconciling your explanation with my understanding.
m
reifiedGenericIsSame(soitd, sostd)
gets inlined as the following
Copy code
(soitd is FBOS<Int>) && (sostd is FBOI<Int>)
which gets translated to
(soitd is FooBar<String,A>) && (sostd is FooBar<Int,A>)
after type erasure it becomes
(soitd is FooBar) && (sostd is FooBar)
That is true because all the variables are of type
FooBar
Remember the generic parameters for
S
and
R
are determined at compile time
a
Do I gather correctly then that
reifiedGenericIsSame(soi, sos)
gets inlined (eventually?) as
(soi is FooBar<String,A>) && (sos is FooBar<Int,A>)
? If so, why would the eventual result be different? (soi and sos are correctly recognized as different types, soitd and sostd are incorrectly recognized as the same type). Or could it possibly be that
reifiedGenericIsSame(soi, sos)
gets inlined (through some different mechanism?) as
(soi is SpecialOfStr<A>) && (sos is SpecialOfInt<A>)
? If so, the typealias is at some fault, in addition to type erasure. Q: Should the typealias be (always?) ignored by the compiler in a
is
comparison? It should be, after all, just sugar.
m
Yes the typealias will be ignored by the
is
and the real value will be used. The difference between
reifiedGenericIsSame(soi, sos)
and
reifiedGenericIsSame(soitd, sostd)
are the declared types of the references (not the objects they point to).
reifiedGenericIsSame(soitd, sostd)
will use
is FBOI
and
is FBOS
, These are type aliases to the same type so the checks always pass.
reifiedGenericIsSame(soi, sos)
will use
is SpecialOfInt
and
is SpecialOfStr
because those are the types of the references at compile time.
type aliases have nothing to do with it.
a
So it is possible, by means of a typealias, to assign a reference so that the reference will have (formally) a different type than that of the underlying (pointed-to) object. That statement is wildly interesting IMHO and fraught with consequences. Does this conflict with the principle that an object has exactly one type, namely the type assigned at instantiation? It appears that, if a typealias can change the type of a reference, thinking of it as sugar as intuition suggested is VERY wrong. Also, how does this reconcile with
Copy code
fun <A, B> isSameType(a: A, b: B): Boolean = a?.let{ outer -> outer::class == b?.let{ it::class } } ?: false
the above, in the example provided, behaves in a manner sometimes contradicting
reifiedGenericIsSame
to the extent that
isSameType(soi, sos)
and
isSameType(soitd, sostd)
behave in a consistent manner.
m
An object is a single type but the
is
operator is checking
is a
relationship. So a
SpecialOfInt
is a
FooBar
so
is
operator returns true. Type alias will not let you change the type of the object.
Copy code
val foo = 5
foo is Int // true
foo is Number // true
foo is Any // true
foo is Int? // true
foo is String // false
foo::class == Int
In your example the
soitd
is a reference that can refer to any
FooBar<Int,A>
It happens to be referencing an object of type
SpecialOfInt
a
Hm.