3 ms·
Have you worked with Kotlin yet? Since 1.5 Result<T> is a valid return type. Inline classes are thin wrappers and compiled out. Data classes automatically give
by scns 5y ago
Have you worked with Kotlin yet? Since 1.5 Result<T> is a valid return type. Inline classes are thin wrappers and compiled out. Data classes automatically give you a toString method. With sealed classes you can implement ADTs.
- ragnese 5y agoI have. I generally like Kotlin, but even that makes it a little too cumbersome to work with "newtypes" such as a NonEmptyString type. Here's what I do for a NotBlankString in Kotlin 1.5+: @JvmInline value class NotBlankString private constructor(private val value: String) : CharSequence { override val length: Int get() = value.length override fun get(index: Int): Char = value[index] override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = value.subSequence(startIndex, endIndex) override fun toString(): String = value companion object { fun of(value: CharSequence): NotBlankString? = if (value.isBlank()) null else NotBlankString(value.toString()) } } The problem is that most APIs in Kotlin and Java (including the standard library as well as almost all third party libraries) work specifically with String, which is a final class. So, using my NotBlankString is a pain in the ass because I have to explicitly call toString() for most APIs. Also, I do implement CharSequence, because String does. But CharSequence is a terrible interface and we should probably just pretend it doesn't exist. I somewhat regret even acknowledging its presence. One of the limitations of value classes is that they cannot implement an interface by delegation, either. So I have to implement CharSequence by hand, instead of writing `: CharSequence by value`. If you define a "newtype" in Kotlin by using a value class, don't forget to override toString to call value.toString(). By default, it's going to print like a data class format: "NotBlankString(value=foo)" But, overall, this is a much better than the situation in many languages. But it's still just awkward enough that I think a lot of people don't bother. In my opinion, a statically typed language should HIGHLY prioritize the ergonomics of defining and using custom defined types. Ideally, I would be able to declare somehow that NotBlankString can do everything a String can do, and therefore be able to pass my NotBlankString type into any function that asks for a String. It would also be better for ergonomics if I could define my own type refinement, instead of needing to call a constructor- kind of like how Kotlin does "smart casting" with null and sealed types: val s: String = getSomeString() if (s is NotBlankString) { doStuff(s) // takes a NotBlankString } else { doOtherStuff() }
- scns 5y agoThank you very much for the detailed writeup. Several new things to learn for me in there. Not being able to use delegation here is a bummer, would be a great fit. While trying to write up a clever solution using [0] i stumbled over String.isNullOrBlank() [0] https://kotlinlang.org/docs/scope-functions.html https://kotlinlang.org/docs/scope-functions.html (edit) if (s.isNullOrBlank()) { doOtherStuff() } else { doStuff(s) // takes a NotBlankString } If you are sure it is not null you can use String.isBlank()