How to check initial value of delegated property?

Hi, I want a property of type Double to not have a negative value, including during class construction. I can do it this way:

import kotlin.properties.*
import kotlin.reflect.KProperty

class NonNeg(nonNeg: Double) {
    var nonNeg: Double by NonNegDelegate(nonNeg)
}

class NonNegDelegate(initVal: Double) {
	private var _nonNeg: Double = if (initVal >= 0.0) initVal else 0.0
	operator fun getValue(thisRef: Any?, property: KProperty<*>): Double {
		return _nonNeg
	}
	operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Double) {
		_nonNeg = if (value >= 0.0) value else 0.0
	}
}

fun main() {
    var myNonNeg = NonNeg(-2.0)
    println("${myNonNeg.nonNeg}")  // 0.0
}

Is there a better way to do this with property delegation but without replicating the check in setValue() in the declaration of _nonNeg? Thanks.

do the check in getValue instead with something like this:

operator fun getValue(thisRef: Any?, property: KProperty<*>): Double {
	return if (_nonNeg > 0.0) _nonNeg else 0.0
}
data class NonNegativeDouble(val value: Double) {
	init {
		require(value >= 0) { "Value must be greater or equal to 0." }
	}
	
	override fun toString() = value.toString()
}

data class NonNegative(val nonNegativeDouble: NonNegativeDouble)

If you want to protect your data constraints this way is probably better.

1 Like

Thanks for the alternative! However, unless I kept the check in setValue(), which would then duplicate the check in setValue() and getValue(), the property would have negative value (even if inaccessible). (Further, if the value is read more often than written, this would run the check on every get . . . .)

Moving the goal post a bit: if the value is only allowed to be initialized to ≥ 0 and later set to ≥ 0 or to retain its present value:

class NonNegDelegate(initVal: Double) {
	private var _nonNeg: Double = if (initVal >= 0.0) initVal else 0.0
	operator fun getValue(thisRef: Any?, property: KProperty<*>): Double {
		return _nonNeg
	}
	operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Double) {
		_nonNeg = if (value >= 0.0) value else _nonNeg
	}
}

is there an alternative that uses property delegation but doesn’t replicate the check?

class NonNegDelegate(initVal: Double) {
	private var nonNeg: Double = initVal
		set(value) {
			require(value >= 0) { "Value must be greater or equal to 0." }
			field = value
		}
	
	init {
		require(nonNeg >= 0) { "Value must be greater or equal to 0." }
	}
	
	operator fun getValue(thisRef: Any?, property: KProperty<*>): Double = nonNeg
	
	operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Double) {
		nonNeg = value
	}
}

The only problem is as you probably noticed is that an initial field initialization doesn’t use setter, so the check needs to be done twice.

Thanks! It is as you said, “initial field initialization doesn’t use setter” summarizes it.

This should do it:

class NonNegDelegate private constructor(){
        constructor(initVal: Double): this() { setValue(null, null, initVal) }
	private var _nonNeg: Double = 0.0
	operator fun getValue(thisRef: Any?, property: KProperty<*>?): Double {
		return _nonNeg
	}
	operator fun setValue(thisRef: Any?, property: KProperty<*>?, value: Double) {
		_nonNeg = if (value >= 0.0) value else _nonNeg
	}
}

Or move your setter logic out into something else

1 Like

That did do it! Yay! Thanks so much!

1 Like

No problem. You can also move that logic into the private property’s setter like this:

class NonNegDelegate private constructor(){
    constructor(initVal: Double): this() { _nonNeg = initVal }
	private var _nonNeg: Double = 0.0
        set(value) { field = if (value >= 0.0) value else field }
	operator fun getValue(thisRef: Any?, property: KProperty<*>?): Double = _nonNeg
	operator fun setValue(thisRef: Any?, property: KProperty<*>?, value: Double) = _nonNeg = value
}
1 Like

Hm, I do like this one better. I guess because the logic is “closer” to the field? One less level of indirection? It’s good to know that one could call setValue() in the other solution though.

BTW, the last function assignment cannot be an assignment, according to the compiler:

	operator fun setValue(thisRef: Any?, property: KProperty<*>?, value: Double) = _nonNeg = value

needs to be:

	operator fun setValue(thisRef: Any?, property: KProperty<*>?, value: Double) { _nonNeg = value }

Thanks!

1 Like

Delegate every time will be call to new queue. Object don’t save copy. If you want lifetime object you must use Lazy delegate like this