Hey, is there a way to generally reference the val...
# getting-started
u
Hey, is there a way to generally reference the value inside a value class? I'm trying to use type-safe database ids, and therefore I need a column adapter
Copy code
class BusinessMessageIdColumnAdapter : ColumnAdapter<BusinessMessageId, String> {
    override fun decode(databaseValue: String): BusinessMessageId = BusinessMessageId(databaseValue)
    override fun encode(value: BusinessMessageId): String = value.value
}
for every type of id, which is annoying I know I could get it via reflection, but .. probably looking for a "hack" like this (the enumValues function)
Copy code
@Suppress("FunctionName") // Emulating a constructor.
inline fun <reified T : Enum<T>> EnumColumnAdapter(): EnumColumnAdapter<T> {
  return EnumColumnAdapter(enumValues())
}
e
on JVM you can do
Copy code
class EnumColumnAdapter<T : Enum<T>>(private val javaClass: Class<T>) {
    fun decode(databaseValue: String): T = java.lang.Enum.valueOf(javaClass, databaseValue)
    fun encode(value: T): String = value.name
}

EnumColumnAdapter(BusinessMessageId::class.java) // etc.
on all platforms you can do
Copy code
class EnumColumnAdapter<T : Enum<T>> @PublishedApi internal constructor(
    private val valuesByName: Map<String, T>
) {
    fun decode(databaseValue: String): T = valuesByName.getValue(databaseValue)
    fun encode(value: T): String = value.name
}

inline fun <reified T : Enum<T>> EnumColumnAdapter(): EnumColumnAdapter<T> =
    EnumColumnAdapter(valuesByName = enumValues<T>().associateBy { it.name })

EnumColumnAdapter<BusinessMessageId>() // etc.
you can't make a inline reified constructor, but you can make a constructor-like function
j
We made all our typesafe strings extend a single StringMicrotype class, and made a single StringMicrotype ColumnAdapter that'll work for all of them. It does mean introducing some class hierarchy which might feel a bit artificial, and a separate adapter for each type of data required. Might work for you though!
u
how do you instantiate it back from string if the adapter only sees the StringMicrotype base class? like @ephemient did?
@ephemient I dont think I follow, the id is not enum, or r u hijacking it somehow?
j
how do you instantiate it back from string
could you give me an example, please?
u
well how do you instantiate BusinessMessageId from string generically, if presume you BusinessMessageId : StringMicrotype and the adapter has a StringMicrotype parameter
e
I assumed BusinessMessageId was an enum
when used genetically a value class is just a normal class
so you could build something like
Copy code
class ColumnAdapter<T>(
    private val encode: (String) -> T,
    private val decode: (T) -> String,
)

ColumnAdapter(
    encode = ::BusinessMessageId,
    decode = BusinessMessageId::value,
)
but you can't get a
::T
constructor for a generic type
<T>
, so that's about as far as you can go
u
yea I wanted to get typeinference like in the enumcolumnadapter, shame
j
Sorry, I was being daft - our StringMicrotypeColumnType is abstract, we have separate column classes, each like
Copy code
class EmailAddressColumnType : StringMicrotypeColumnType<EmailAddress>() {
    override fun fromString(s: String) = EmailAddress(s)
}
A decent bit of logic is still shared, but not as neat as you're after
e
with some horrible reflection,
Copy code
@JvmInline
value class Id(val value: String)

val converter = Converter<Id>()
println(converter.encode(Id("Hello, ")) + converter.decode("world!"))
// => Hello, Id(value=world!)
note that the value-ness of
Id
doesn't come into play at all: when generic, you're dealing with the class
Id
, not the underlying
String
representation, just like how
<T : Int>
is also always boxed
u
thank you