fun guard(predicate: () -> Boolean) : Guard {
return Guard(predicate)
}
data class Guard(val predicate: () -> Boolean) {
infix fun orElse(trap: () -> Nothing) {
if (!predicate()) {
trap()
}
// can we assert as post-condition that `predicate` holds?
}
}
Note “return” type Nothing which is to imitate the semantics of Swift’s guard (which are not allowed to fall through).
Syntactically, this comes close (no assignment, of course):
fun foo(a: Int?) {
guard { a != null } orElse {
return@foo // would make sense, but isn't allowed
throw RuntimeException("blerg") // throwing is what you do
}
assert(a != null) // _We_ know that, but Kotlin doesn't
println(a + 1) // no smart cast :(
}
As the comments indicate,
we can not escape the orElse “branch” by returning from a surrounding scope, and
Kotlin is not able to infer that the guarding predicate is true if orElse returns.
The second point bothers me. Even if the compiler can’t infer the assertion (if feeld like it should be able to, in principle, at least for “simple” predicates) on its own, if we could tell it that predicate() ==> trap() called or orElse() terminates => predicate(), it could help it along. Are there some post-condition annotations that can do that?
That we don’t get a smart-cast after the assertion is so weird I’m inclined to call bug.
I guess the null check is just an example, because otherwise you could just use
a ?: run { ... }
That way kotlin also knows about a not being null later.
I’m not entirely sure why that is. Have you tried declaring orElse as inline? It might also have to do something with the declared return type of Nothing (just speculation though).
There is a keep about that here. There is a timeline in there as well. You can start to see the first implementation in Kotlin 1.3.
That does it. Good call, thanks!
(Not 100% why non-line doesn’t work… probably something about which assumptions you can make about where that closure is passed to?)
In Swift guard takes the same expressions as if even though all examples emphasizes unwrapping (converting from nullable to not-nullable in Kotlin terms).
How about
fun <T> T.guard(vararg cond: Boolean, otherwise: T.()->Nothing): T {
if(cond.all { !it }) {
return this
} else {
otherwise()
}
}
or for unwrapping:
fun <T> T?.guard(vararg cond: T.()->Boolean, otherwise: T?.()->Nothing): T {
val value = this ?: otherwise()
if(cond.all { value.it() }) {
return value
} else {
otherwise()
}
}
They unlike Swift’s guard allows to check multiple conditions. Also return in case of unacceptable parameters doesn’t make sense. This is why I made the handler to return Nothing, which will force the closure to throw Exception.
The orElse DSL I proposed above can not (yet?) be implemented since contracts are only implemented for top-level functions. Similarly, a closure predicate is not (yet?) supported.
Note also that we’re not (yet?) able to express calls(orElse) implies !predicate or, equivalently, predicate implies callsInPlace(orElse, AT_LEAST_ONCE) which would be needed to fully describe the contract of guard. While that’s redundant here because of Nothing, for any other return type we’d need it.
I know, that was just an example.
What I meant is that you can have as many condition checks as you want, with similar to guard syntax.
val value: Any = "Hell"
if (value is String && value.length >= 5) else {
log("value is either not a string or is to short, returning")
return
}
log("Char at index 4 is ${value[4]}")
val value = nullableString?.takeIf { it is String && it.length >= 5 } ?: return Unit.also {
log("value is either not a string or is to short, returning")
}
log("Char at index 4 is ${value[4]}")
Also note that if (str == null) return also works instead of your original if (str != null) else { return }.
I would suggest not to use code like that. It is very easy to misread it. I didn’t notice there was an else at all when I read it the first time. Not seeing the else made me think the condition for returning is inversed. This can lead to misinterpretations and hence bugs.
And that is the feature of guard I was looking to emulate! It’s not (only) about what is executed, but more about what the compiler can require and assume.
Trying to get if or even the elvis operator to operate as guard feels like a pretty sub par attempt and seems to miss the more useful aspects of guard.
For something like this:
guard let a = self.nullableA, let b = self.nullableB, let c = self.nullableC else {
log("something")
return
}
The Kotlin version:
val a = this.nullableA
val b = this.nullableB
val c = this.nullableC
if(a == null || b == null || c == null) {
log("something")
return
}
mark_gilchrist captures a nice solution, however, a baked in language feature (along with a ternary operator would be heaps closer to concise syntactic sugar nirvana for me.
Kotlin has a ternary operator! In fact, any operator with three operands is ternary; but Kotlin has the same ternary operator as Java (and C). It’s spelled differently: if (…) … else … instead of … ? … : …, but it means exactly the same. In particular, it’s an expression that gives a value, so can be used in all the same places as Java’s one. And while it takes a few extra characters to type, it’s arguably clearer and easier to read.
I really don’t understand why people keep asking for something that’s already there…