Gerard Bosch
03/17/2021, 6:38 PMfun <T> T?.onNull(effects: () -> Unit): T? {
if (this == null) effects()
return this
}
What I feel strange is that by defining the above extension, I'm allowed to invoke it on non-nullable types, which results a little counter-intuitive. I mean, given the above I can do:
val str: String = "helo" // non-nullable type
str.onNull { print("Will never print as I'm not actually null") } // <-- Why the compiler allows this?
Is it possible to restrict it just to nullables as in T?
Thx 🙂Marc Knaup
03/17/2021, 6:41 PM@OptIn(ExperimentalContracts::class)
inline fun <T : Any> T?.ifNull(defaultValue: () -> T): T {
contract {
callsInPlace(defaultValue, InvocationKind.AT_MOST_ONCE)
}
return this ?: defaultValue()
}nanodeath
03/17/2021, 6:44 PMval foo = thisMightBeNull ?: effects()Marc Knaup
03/17/2021, 6:44 PM@JvmName("ifNullOnNonNull")
@Deprecated(message = "Use on nullable types only.", replaceWith = ReplaceWith("this"), level = DeprecationLevel.ERROR)
inline fun <T : Any> T.ifNull(defaultValue: () -> T): T =
thisMarc Knaup
03/17/2021, 6:45 PMZach Klippenstein (he/him) [MOD]
03/17/2021, 6:45 PMfun <T> T?.onNull(effects: () -> Unit): T? {
if (this == null) effects()
return this
}
@Suppress("UNUSED_PARAMETER", "unused")
@Deprecated("Only supported for nullable types.", level = ERROR)
@JvmName("onNull-non-nullable")
fun <T : Any> T.onNull(effects: () -> Unit): T {
throw UnsupportedOperationException("Only supported for nullable types.")
}
Because the bounded-T overload is more specific, it’s the overload that will be selected for non-nullable types. The ERROR-level deprecation causes the compiler to then complain when that happens.Gerard Bosch
03/17/2021, 8:08 PMthis , I wanted to use it in operation pipelines for example to log on null.Gerard Bosch
03/17/2021, 8:12 PMUnsupportedOperationException would actually be thrown? or the compiler would *always/*in all cases make it fail at compile-time?CLOVIS
03/17/2021, 8:35 PMnull itself, it's Nothing?Michal Klimczak
03/17/2021, 8:39 PMNothing? but it's not the same thing as "the type of null"Marc Knaup
03/17/2021, 8:41 PMZach Klippenstein (he/him) [MOD]
03/17/2021, 9:22 PMIs there any situation in which thatI think consumers couldwould actually be thrown?UnsupportedOperationException
@Suppress("DEPRECATION") to let them call that function.Gerard Bosch
03/17/2021, 11:03 PMT? with Nothing for the overloaded one:
fun <T : Any> T.onNull(effects: () -> Unit): Nothing {
make any difference or improvement?
I've seen the Nothing doc:
Nothing has no instances. You can use Nothing to represent "a value that never exists": for example, if a function has the return type of Nothing, it means that it never returns (always throws an exception)
edrd
03/18/2021, 12:30 AMdoSomethingThatMayReturnNull()
.also { it ?: println("Null") }
.doSomethingElse()edrd
03/18/2021, 12:31 AMifNull approach is more idiomaticGerard Bosch
03/18/2021, 10:40 AMonNull / ifNull easier to read indeed.
Anyone has thoughts about using Nothing as the return type of the overload that throws instead of T?, any difference? - Both seem to workCLOVIS
03/18/2021, 11:32 AMNothing here: it means “this function doesn't return”, not “you shouldn't call this function”. Since you have the error depreciation and the throw inside the function, it won't make a difference.
But I don't think you should have this function at all.Gerard Bosch
03/18/2021, 1:26 PMCLOVIS
03/18/2021, 2:00 PM.also { it ?: effects() } would be better.Gerard Bosch
03/18/2021, 2:40 PM.also { it ?: effects() }
.onNull { effects() }
if I read both of them, I personally find the first has more cognitive complexity and is less clear (also more verbosity). The onNull encapsulates that behaviour, looks a good abstraction to me.
But I'm also interested in hearing more oppinions 🙃edrd
03/18/2021, 3:18 PMMarc Knaup
03/18/2021, 3:18 PM.let { it ?: foo } difficult to parse esp. when the expressions become longer or more complex.
I have 104 matches of .ifNull in my project. It’s very simple and useful :)
Btw, it should be ifNull if it returns something, analogous to ifEmpty of CharSequence and Collection.
on… is typically for side-effects and only returns this unchanged, analogous to onEach of Iterable.
See my example: https://kotlinlang.slack.com/archives/C0922A726/p1616006471054600?thread_ts=1616006300.054500&cid=C0922A726Marc Knaup
03/18/2021, 3:20 PM.ifNull on a non-null type is like calling ?.anything on a non-null receiver. The IDE warns about that.
Unfortunately we have no way of telling the compiler or IDE to issue a warning or error other than using the overload trick.Gerard Bosch
03/18/2021, 3:30 PMonNull() as my intention was a function to just perform just side-effects (see the Unit in the signature), the same way Option.onEmpty() would do. But as it applies over a nullable type instead of over a container, it should be onNull().
Then I also thought about an
fun <T> T?.orElse(ifNull: (T?) -> T?): T?
to return another thing like a fallback.Marc Knaup
03/18/2021, 3:33 PMonNull returns this then it makes sense 🙂edrd
03/18/2021, 3:34 PMAny?, so you could just write
fun <T> T.orElse(ifNull: (T) -> T): T
But I think it would make more sense to have orElse return a non-nullable, otherwise you could just use `let`:
fun <T : Any> T?.orElse(ifNull: (T?) -> T): T
Or if you want to allow another return type:
fun <T, R> T.orElse(ifNull: (T) -> R): RCLOVIS
03/18/2021, 3:35 PMifNull and not onNull though.Marc Knaup
03/18/2021, 3:37 PMMarc Knaup
03/18/2021, 3:38 PMifEmpty is implemented by stdlib isn’t legal Kotlin code. You cannot just copy & paste it :)CLOVIS
03/18/2021, 3:39 PMMarc Knaup
03/18/2021, 3:40 PMpublic inline fun <C, R> C.ifEmpty(defaultValue: () -> R): R where C : Collection<*>, C : R =
if (isEmpty()) defaultValue() else thisMarc Knaup
03/18/2021, 3:40 PMdefaultValue lambda. Pretty neat.CLOVIS
03/18/2021, 3:52 PMfilterIsInstance<A, B>()Marc Knaup
03/18/2021, 3:52 PMCLOVIS
03/18/2021, 3:56 PM.filterIsInstance<A>().filterIsInstance<B>(), except it would be safe (if you write that, you get C<B>, but you'd want C<A & B> (using Java notation).
The weird part is that if you have two specific interfaces, you can define that (where C : InterfaceA, C : InterfaceB) but that doesn't work with generics, even when inline.Marc Knaup
03/18/2021, 3:57 PMMarc Knaup
03/18/2021, 3:57 PM.filterIsInstance<A & B>()
.filterIsInstance<A | B>()Gerard Bosch
03/18/2021, 4:27 PM.orElse() over .let is at the call site, as if I'm not wrong, the let requires to call like ?.let { } to achieve that behaviour. Forgotting the ? is very easy and also confusing. WDYT?
fun <T> T?.orElse(ifNull: () -> T?): T? {
return this ?: ifNull()
}Marc Knaup
03/18/2021, 4:34 PMorElse. It typically refers to absence of value, not null.
What do you mean by forgetting ??Gerard Bosch
03/18/2021, 4:39 PMsomething()
?.let { somethingElse() }
the same than?
something()
.let { somethingElse() }
If I'm not wrong the above has different behaviours, hasn't it?
And
something()
.orElse { somethingElse() }
would cause no confusion. I'm thinking all of this but I'm a Kotlin newbie, so I may be wrong 🙂Gerard Bosch
03/18/2021, 4:43 PM.let { it ?: foo } , don't take the above into account.Marc Knaup
03/18/2021, 4:50 PM.let { it ?: foo } and ?.let { it ?: foo } the IDE would warn about the latter making no sense.Gerard Bosch
03/18/2021, 4:51 PM.let { it ?: somethingElse(...) }
vs
.orElse { somethingElse(...) }
for readability. Does it make sense now?Youssef Shoaib [MOD]
03/18/2021, 5:39 PM@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
import kotlin.internal.*
inline fun <T: Any> @Exact T?.onNull(effects: () -> Unit): T? {
if (this == null) effects()
return this
}
fun main() {
val str: String = "hello" // non-nullable type
str.onNull { println("The compiler prevents this call from compiling") }
val nullableStr: String? = str
nullableStr.onNull { println("but this is allowed")}
val nullableStr2: String? = null
nullableStr2.onNull { println("only this is null though")}
println("hello world")
}Gerard Bosch
03/18/2021, 6:06 PM.onNull over non-nullable type." 👏 👏 👏
But maybe this is a bigger hack than overloading the function, and more fragile I guess? If the internal annotations change in future versions. WDYT?
Also the error message in the IDE is more cryptic compared to the deprecated message.Marc Knaup
03/18/2021, 6:07 PMMarc Knaup
03/18/2021, 6:09 PMYoussef Shoaib [MOD]
03/18/2021, 6:11 PMYoussef Shoaib [MOD]
03/18/2021, 6:13 PMYoussef Shoaib [MOD]
03/18/2021, 6:13 PMMarc Knaup
03/18/2021, 6:14 PMMarc Knaup
03/18/2021, 6:14 PMGerard Bosch
03/18/2021, 6:18 PM@DeprecatedMarc Knaup
03/18/2021, 6:18 PMGerard Bosch
03/18/2021, 6:22 PMMarc Knaup
03/18/2021, 6:23 PMGerard Bosch
03/18/2021, 6:26 PM.let { it ?: somethingElse(...) }
vs
.orElse { somethingElse(...) }
for readability. Does it make sense now?Gerard Bosch
03/18/2021, 6:27 PMMarc Knaup
03/18/2021, 6:28 PMorElse is a bad fit 🙂
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/get-or-else.html
But yeah, I’d avoid mixing .let with ?:.Gerard Bosch
03/18/2021, 6:29 PMgetOrElse suits better?Marc Knaup
03/18/2021, 6:29 PMOrElse is for absence of value, not for null value.Marc Knaup
03/18/2021, 6:30 PMnull then I’d use ifNull similar to ifEmpty which already exists.Gerard Bosch
03/18/2021, 6:30 PMMarc Knaup
03/18/2021, 6:30 PMOption in KotlinGerard Bosch
03/18/2021, 6:30 PMGerard Bosch
03/18/2021, 6:30 PMMarc Knaup
03/18/2021, 6:30 PMOption.orElse 😉Gerard Bosch
03/18/2021, 6:31 PMMarc Knaup
03/18/2021, 6:31 PMOptional.orElse comes closest. There the name would fit Kotlin’s naming scheme because it would return something if there’s an absence of value.
null on the other hand is not absence.Marc Knaup
03/18/2021, 6:32 PMGerard Bosch
03/18/2021, 6:33 PMnull is used as a semantic value of 'no result'Marc Knaup
03/18/2021, 6:33 PMifNull then 🙂
Super clear just from reading it.Gerard Bosch
03/18/2021, 6:34 PMonNull(effects) and ifNull(fallback) ? not confusing?Marc Knaup
03/18/2021, 6:35 PMif… the lambda on… this and is only a side-effect.
Consistency matters here.Gerard Bosch
03/18/2021, 6:36 PMMarc Knaup
03/18/2021, 6:36 PMlistOf(1,2,3)
.onEach { println(it) }
.ifEmpty { null }