https://kotlinlang.org logo
#announcements
Title
# announcements
n

nwh

07/15/2019, 7:29 PM
Is it possible to seamlessly wrap something like an
Optional
? Where there are three valid values: regular value, null, nothing. I'm using a data class so a
lateinit var
isn't exactly the best solution
j

jw

07/15/2019, 7:31 PM
Seamlessly? No. Always. Otherwise, yes. Most optionals do not distinguish between null and absence so you'll probably need to write one that supports nullable values and absence as orthogonal concepts.
n

nwh

07/15/2019, 7:33 PM
I'd just like to avoid the boilerplate of
object.value = uselessWrapper(value)
and
object.value.get()
, it gets so crowded
s

streetsofboston

07/15/2019, 7:44 PM
What about
val value: Optional<T>?
, where a
null
value is ‘nothing/unknown’, a
Optional.None
value is ‘not-present’ and
Optional.Some<T>
is an actual regular value?
j

jw

07/15/2019, 7:45 PM
yeah i mean it sounds like what you want is probably a nullable sealed type where "nothing" is more semantically represented in the type system instead of with a generic box
n

nwh

07/15/2019, 7:52 PM
I suppose that could work but I'm not sure it's superior. And yeah that's about right, jw. Or first class support for Optionals in a similar way to nullables (with an isPresent variable)
g

groostav

07/15/2019, 7:54 PM
The fact that your domain has a different meaning for
null
than it does for
empty/nothing
to me means this is a very subtle problem. What does
null
encode? What does
empty
encode? At this point I'd probably make a sealed class that names those two concepts, and go from there. Downstack, where you can be certain you don't care about one of those cases, (eg in a parameter), use a simple nullable-type instead of this sealed class. But if its a field for a state machine, you would need to completely eliminate one of those states to avoid the "useless boilerplate"
n

nwh

07/15/2019, 8:06 PM
I mostly agree @groostav, it's mostly a semantic choice. For pushing values to an API, it merges incoming data with what's present. So if a field is completely omitted in the request then no changes are made, while null empties out the value, and all other values are set directly. Basically, passing "" would be the same as an empty optional (empty stored value) while passing null would be equivalent to passing a null optional (noop). Which is a totally fine solution, I was just curious about other possibilities. But an empty optional is a more clear statement of deliberately passing nothing, if that makes sense.
s

streetsofboston

07/15/2019, 8:18 PM
@nwh Something like this?:
Copy code
sealed class Optional<out T : Any> {
    object None : Optional<Nothing>()
    object Null : Optional<Nothing>()
    data class Some<out T : Any>(val value: T) : Optional<T>()
}
n

nwh

07/15/2019, 8:22 PM
That's a good solution but it's very similar to using Optional in terms of the boilerplate :(
s

streetsofboston

07/15/2019, 8:22 PM
There are some ‘tricks’ around the boiler plate using operator functions 🙂
Some tricks, using the
not
or
component1
operator (use one of them), where you tease out values if it is a Some and short-cut a None value all together (
bind
).
Copy code
private object IsNoneException : Exception()

sealed class Optional<out T> {
    object None : Optional<Nothing>()
    data class Some<out T>(val value: T) : Optional<T>()

    companion object {
        fun <T> bind(block: () -> T): Optional<T> = try {
            Some(block())
        } catch (_: IsNoneException) {
            None
        }
    }
}

operator fun <T> Optional<T>.not(): T {
    return when (this) {
        is Optional.Some -> value
        is Optional.None -> throw IsNoneException
    }
}

operator fun <T> Optional<T>.component1(): T {
    return when (this) {
        is Optional.Some -> value
        is Optional.None -> throw IsNoneException
    }
}

fun main() {
    val optional: Optional<Int?> = Optional.Some(2)

    val result1 = Optional.bind {
        val value = !optional
        if (value != null) value * 2 else 0
    }

    val result2 = Optional.bind {
        val (value) = optional
        if (value != null) value * 2 else 0
    }

    println("$result1 and $result2")
}
n

nwh

07/15/2019, 9:34 PM
Interesting, thanks
4 Views