Henrik Johansson
11/22/2021, 3:04 PMHenrik Johansson
11/22/2021, 3:06 PMtypealias SirName: String
typealias Name: String
Rob Elliot
11/22/2021, 3:08 PMHenrik Johansson
11/22/2021, 3:08 PMFullName(sirName, name)
and if switch them around I get a compilation error because the types are different.Henrik Johansson
11/22/2021, 3:08 PMHenrik Johansson
11/22/2021, 3:09 PMEmil Kantis
11/22/2021, 3:10 PMHenrik Johansson
11/22/2021, 3:10 PMRob Elliot
11/22/2021, 3:10 PMvalue class Name(val value: String)
will give you the type safety but not the convenience - Name
will not be a subtype of String
, you’ll have to pass Name.value
if you need a String
.Henrik Johansson
11/22/2021, 3:11 PMRob Elliot
11/22/2021, 3:11 PMName
will actually be a String
.Henrik Johansson
11/22/2021, 3:12 PMRob Elliot
11/22/2021, 3:14 PMvalue class
(rather than say a data class
) is that it’s mostly optimised away at compile time so that the generated bytecode is the same as if you had just been using the type of its value (String
in this case).
Occasionally that’s not possible, so a “real” Name
class is also generated and the value may sometimes be boxed as that.Henrik Johansson
11/22/2021, 3:18 PMJoffrey
11/24/2021, 8:01 PMHenrik Johansson
11/25/2021, 7:03 AMJoffrey
11/25/2021, 11:25 AMFullName
) that wants to use 2 single-field value classes. So you don't need Valhalla at all for this example:
class FullName(val surName: SurName, val name: FirstName)
@JvmInline
value class SurName(val value: String)
@JvmInline
value class FirstName(val value: String)
The point of these value classes is to not be assignable to other types. So you can't give a SurName
object where a FirstName
is expected (this is the type safety benefit).
As have been said before, conversely, with value classes you can't assign a plain string to a SurName
nor assign a SurName
to a plain string (unlike with type aliases), but this to me is also part of the point of using value classes. Conversions like this must be conscious/explicit.
They are in fact independent types.
So to create a FullName
out of strings, you would need to write:
val name = FullName(SurName("Lennon"), FirtName("John"))
Rob Elliot
11/25/2021, 12:11 PMvalue class Name(val name : String)
being treated as a subtype of String
- so you can’t use a String
where a Name
is needed, but you can use a Name
where a String
is needed. It would just be syntax sugar for Name.name
, and at bytecode time it’s all just a String
anyway. This would give (nearly?) all the advantages of type safety - a value class is a specialisation of the type of its value.
But it would open up a whole bunch of issues around polymorphism. For instance, calling Name("Jana").toString()
returns Name(Jana)
. But pass Name("Jana")
to a function that takes a String
and calling toString
on that String
of course just returns Jana
. Which totally breaks the expectation that they are (conceptually!) the same object.
So all told, much cleaner and safer and less surprising if you have to explicitly dereference the wrapped property when its type is requested.Rob Elliot
11/25/2021, 12:20 PMvalue class X(val value: T)
automatically generated fun T.toX() = X(this)
- I so much prefer postfix conversion functions!)Henrik Johansson
11/25/2021, 1:56 PMHenrik Johansson
11/25/2021, 1:56 PMJoffrey
11/25/2021, 2:03 PM.value
.
One other problem for instance is that String
is supposed to be final, so it would be valid to expect that the runtime class of any variable of type String
is in fact String.class
, which would not be the case here if the value is boxed. Value classes don't guarantee that the inner value is always unboxed - at least that's the idea in Kotlin's value classes design. The goal is for the programmer to give up on identity so that the compiler is allowed to optimize this way (or in other ways), but nothing is really guaranteed (as you noted).