Skip to content

Instantly share code, notes, and snippets.

@ajoz
Last active May 31, 2020 22:24
Show Gist options
  • Save ajoz/f1c14da74501e9186e8d06b2edf8ec66 to your computer and use it in GitHub Desktop.
Save ajoz/f1c14da74501e9186e8d06b2edf8ec66 to your computer and use it in GitHub Desktop.
Refined types in Kotlin
class Refined<A : Any>(
default: A,
private val errorMsgGen: (A) -> String = {
"Value: $it does not meet its constraints!"
},
private val validator: (A) -> Boolean = { true }
) : ReadWriteProperty<Refined<A>?, A> {
private var backingField: A = default
override operator fun getValue(thisRef: Refined<A>?, property: KProperty<*>): A {
return backingField
}
override operator fun setValue(thisRef: Refined<A>?, property: KProperty<*>, value: A) {
if (validator(value)) {
backingField = value
} else {
throw IllegalStateException(errorMsgGen(value))
}
}
}
// this will allow to enhance the error message with the typename
// a bit of experimentation with the `thisRef` showed that it
// always returns null
inline fun <reified A : Any> Refined(
default: A,
noinline validator: (A) -> Boolean
): Refined<A> {
return Refined(
default,
{
"Value: $it does not meet type: ${A::class.java.simpleName} constraints!"
},
validator
)
}
fun PositiveInt() =
Refined(0) { it > 0 }
fun NegativeInt() =
Refined(0) { it < 0 }
fun <A> NonEmptyList(vararg elements: A) =
Refined(elements.toList()) { it.isNotEmpty() }
fun main() {
// usage example
var positiveInteger: Int by NegativeInt()
positiveInteger = -1
var nel: List<String> by NonEmptyList("this", "is", "a", "test")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment