Skip to content

Instantly share code, notes, and snippets.

@apatrida
Last active May 25, 2018 15:06
Show Gist options
  • Save apatrida/bde40641ebe53707afb65e4ba6249148 to your computer and use it in GitHub Desktop.
Save apatrida/bde40641ebe53707afb65e4ba6249148 to your computer and use it in GitHub Desktop.
func submit() {
guard let name = nameField.text else {
show("No name to submit")
return
}
guard let address = addressField.text else {
show("No address to submit")
return
}
guard let phone = phoneField.text else {
show("No phone to submit")
return
}
sendToServer(name, address: address, phone: phone)
}
func sendToServer(name: String, address: String, phone: String) {
// ...
}
// Guard-like extension function, maybe better called "assertNotNull" or it could be on an Optional instead of nullable
// note: this function returns a non null value of type T given a possibly null value of T?
// unless the value is null, in which case the lambda is invoked and it must return from the enclosing
// function or an exception is thrown preventing the null from affecting surrounding code.
i
inline fun <T: Any> T?.guard(onNullDo: ()->Unit): T {
if (this == null) {
onNullDo()
throw IllegalStateException("You must return from within the null guard block")
}
return this
}
// example using guard extension
fun submit() {
val name = nameField.text.guard {
show("No name to submit")
return // returns from submit() in case you are wondering, same as return@submit
}
val address = addressField.text.guard {
show("No address to submit")
return
}
val phone = phoneField.text.guard {
show("No phone to submit")
return
}
// name, address, and phone are all non-null at this point, guaranteed
sendToServer(name, address, phone)
}
fun sendToServer(name: String, address: String, phone: String) {
println("Sent $name, $address, $phone")
}
// Guard-like inline function, maybe better called "assertNotNull" or it could be on an Optional instead of nullable
// note: this function returns a non null value of type T given a possibly null value of T?
// unless the value is null, in which case the lambda is invoked and it must return from the enclosing
// function or an exception is thrown preventing the null from affecting surrounding code.
inline fun <T: Any> guard(nullableThing: T?, onNullDo: ()->Unit): T {
if (nullableThing == null) {
onNullDo()
throw IllegalStateException("You must return from within the null guard block")
}
return nullableThing
}
fun submit() {
val name = guard(nameField.text) {
show("No name to submit")
return // returns from submit() in case you are wondering, same as return@submit
}
val address = guard(addressField.text) {
show("No address to submit")
return
}
val phone = guard(phoneField.text) {
show("No phone to submit")
return
}
// name, address, and phone are all non-null at this point, guaranteed
sendToServer(name, address, phone)
}
fun sendToServer(name: String, address: String, phone: String) {
println("Sent $name, $address, $phone")
}
// Kotlin things that already act like guard. The `run` function could be changed to `let` or other similar function,
// or alias one of those to a function named `guard`
//
// 5 examples follow...
// === EXAMPLE 1: smart cast after null check
val something: String? = unknownResult()
if (something == null) {
return
}
// valid, because smart cast due to flow analysis shows something can never be null here
functionThatCannotAcceptNull(something)
// === EXAMPLE 2: Elvis operator taking action on null, smart cast due to return
val otherThing: String = unknownResult() ?: run {
reportError("bad bad bad!'")
return
}
// valid, because smart cast due to flow analysis shows otherThing can never be null here
functionThatCannotAcceptNull(otherThing)
// === EXAMPLE 3: Elvis operator with exception causes later smart cast
val lastThing: String = unknownResult() ?: throw IllegalStateException("bad data!")
// valid, because smart cast due to flow analysis shows lastThing can never be null here
functionThatCannotAcceptNull(lastThing)
// === EXMAPLE 4: make it look more guardy
val guardedThing: String = unknownResult<String>() guard {
reportError("bad bad bad")
return
}
// valid, because smart cast due to flow analysis shows guardedThing can never be null here
functionThatCannotAcceptNull(guardedThing)
// ... and this uses the following extension function marked infix just for fun (so space instead of "." to invoke)
inline infix fun <T: Any> T?.guard(onNullDo: ()->Unit): T {
if (this == null) {
onNullDo()
throw IllegalStateException("You must return from within the null guard block")
}
return this
}
// === EXMAPLE 5: make it look more guardy #2 but still use Elvis operator
val protectedThing: String = unknownResult() ?: guard {
reportError("bad bad bad")
return
}
// valid, because smart cast due to flow analysis shows protectedThing can never be null here
functionThatCannotAcceptNull(protectedThing)
// ... which requires this function basically aliasing `run` to `guard`:
inline fun <R> guardx(block: () -> R): R = run(block)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment