Quick "getting started" type question - is this a ...
# getting-started
v
Quick "getting started" type question - is this a valid definition of a data class?
data class Name(val title: String, val lowerTitle = title.lowercase())
? I.e. a default value dependent on the value of another field? It seems to compile...
j
nitpicking on the example itself, A class like this would let me create a
Name("My Title", "TOTALLY NOT MY TITLE Or LOWER")
moving lowerTitle into a val outside the constructor is probably what you really want. The only downside is that it won't be a part of the toString method
v
In real code, it's not just lowercase. And it needs to be in the constructor because it's a Serialized class. It's working but I'm not sure it's a good idea, as you say, could be open to abuse.
j
also keep in mind calling
.copy
will break the relationship between these properties
v
They are allowed to be unrelated in my usecase, it's more about providing a calculated default value if the "lowerTitle" isn't supplied. Though ideally I'd want some sort of contract in place to ensure the value is valid... The true class is:
Copy code
@Serializable
data class PostMetadata(val title: String, val template: String = "post", val slug: String = title.toSlug(), val date: LocalDate)
// and String extension function 'toSlug()'...
fun String.toSlug() : String {
    val reserved = "[;/?:@&=+\$, ]"
    return this.replace(Regex(pattern = reserved),"-").lowercase()
}
I do feel I'm abusing power here.
r
I think it's fine - but I'd define a
Slug
class (as a
value class
if that works OK for you) and enforce the invariant in its constructor.
v
Thanks @Rob Elliot - I have yet to learn value classes (though I'm suddenly, pointlessly, exploring context receivers when I should be focusing on more important things!)
r
Doesn't have to be a value class, could be a plain old data class with one value. The important bit is that by having a checked type that enforces the invariant you ensure that you can't end up with an invalid slug.
A value class is just a way to get that without actually boxing the string in another object in memory. Basically
value class Slug
only exists at compile time, at runtime it will just be a String and the constructor will just be a static method that calls
require(slug.matches(legitimate))
.
(That's not quite the whole story - there are times when the compiler has to box the value in a real
Slug
instance at runtime - but I can't remember when that happens...)