Search code examples
kotlinnullablekotlin-null-safety

Daisy Chaining Elvis Operators?


This is sorta ugly:

override var x1: Double = 0.0 //x-coordinate for object
    get() =
        if(hasParent) { //if there is no parent, we can't have an x coord anyway - nowhere to draw this object
            if (hasPrev) { //we won't always have a previous event, thus the other branch
                prev!!.x2 + parent!!.mEventSpacing
            } else {
                parent!!.mEventStartX
            }
        } else {
            field
        }

Is it possible to replace it with something like the following?

override var x1: Double = 0.0
    get() = (prev?.x2 + parent?.mEventSpacing) ?: parent?.mEventStartX ?: field

The logic is obviously not right there, but is there something close to this? Or would it require some serious fandangling to do something that simple?


Solution

  • Assume that hasParent = parent != null and hasPrev = prev != null.
    Also assume parent and prev are defined as val, not var in the if or when statements because I don't like to add !! many times.

    What you are doing is, using when expression:

    override var x1: Double = 0.0 //x-coordinate for object
        get() = when {
            parent != null && prev != null -> prev.x2 + parent.mEventSpacing
            parent != null && prev == null -> parent.mEventSpacing
            parent == null -> field
        }
    

    You can use the combination of ?. operator and let function (refer to Safe Calls section):

    override var x1: Double = 0.0 //x-coordinate for object
        get() =
            parent?.let { pr -> prev?.let { pv -> pv.x2 + pr.mEventSpacing } ?: pr.mEventSpacing } ?: field
    

    let function executes the parameter block whose parameter is what called let, and return the last expression in the parameter block.

    For example, 1.let { it + 2 } is 3.

    x?.let executes the let function if x is not null. i.e., x?.let { ... } is the same as if (x != null) x.let { ... } else null).

    Here,

    if parent == null, then
    parent?.let { pr -> prev?.let { pv -> pv.x2 + pr.mEventSpacing } ?: pr.mEventSpacing } is just null.

    if parent != null, then
    parent?.let { pr -> prev?.let { pv -> pv.x2 + pr.mEventSpacing } ?: pr.mEventSpacing } is the same as prev?.let { pv -> pv.x2 + parent!!.mEventSpacing } ?: parent!!.mEventSpacing

    You can shorten this. if prev == null, then prev.x2 can be treated as 0. So

    override var x1: Double = 0.0 //x-coordinate for object
        get() =
            parent?.let { pr -> (prev?.let { pv -> pv.x2 } ?: 0) + pr.mEventSpacing } ?: field
    

    You can further shorten this by using it in the inner let function.

    override var x1: Double = 0.0 //x-coordinate for object
        get() =
            parent?.let { pr -> (prev?.let { it.x2 } ?: 0) + pr.mEventSpacing } ?: field
    

    Also, you can use combination of run and let. (Google kotlin run or kotlin run vs let if you don't know it.)

    override var x1: Double = 0.0 //x-coordinate for object
        get() =
            parent?.run { (prev?.let { it.x2 } ?: 0) + mEventSpacing } ?: field
    

    But I think using when is the eastest to understand.


    Well, if parent and prev were defined as var, using when expression with !! can raise a NPE, whereas using ?.let, ?.run, and ?: will never raise a NPE in a multi-core device. To prevent this, you can define local vals with the current values in the getter function so that you will never need to use the !! operator.