Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing a type-safe class hierarchy w/ a nullable value

Tags:

kotlin

I (often) have a resource with two states, pre-created and post-created, where both states have the same fields except for an id field. id is null in the pre-created state and non-null in the post-created state.

I would like to define and use this resource in a clean and type-safe way.

It's common to represent this ID field as a nullable, which handles both scenarios with minimal boilerplate in the class definition. The problem is that it creates a lot of boilerplate in the business logic because you can't assert whether a resource is pre-created or post-created by looking at its type.

Here is an example of the nullable approach:

data class Resource(val id: String?, val property: String)

This is simple to define, but not as simple to handle with due to lack of compile-time guarantees.

Here's an example of a more type-safe approach:

sealed class Resource(val property: String) {
  class WithoutID(property: String): Resource(property)
  class WithID(val id: String, property: String): Resource(property)
}

This allows me to pass around Resource.WithID and Resource.WithoutID, which have all the same fields and methods, except for id.

One inconvenience with this type-safe approach is that the resource definition code gets quite bloated when you have many property fields. This bloating makes the code harder to read.

I'm wondering if there's an alternative approach with less boilerplate, or if Kotlin has any features that make this kind of thing simpler.

like image 212
Cam Hashemi Avatar asked Nov 06 '22 16:11

Cam Hashemi


1 Answers

What about defining

sealed class MayHaveId<T> { abstract val record: T }
class WithId<T>(val id: String, override val record: T): MayHaveId<T>()
class WithoutId<T>(override val record: T): MayHaveId<T>()

class Resource(val property: String)
// and other similar types

and using WithId<Resource> and WithoutId<Resource>? In Scala you could add an implicit conversion from MayHaveId<T> to T, but not in Kotlin, alas, nor can you write : T by record. Still should be clean enough to use.

like image 72
Alexey Romanov Avatar answered Nov 26 '22 15:11

Alexey Romanov