Skip to content
This repository was archived by the owner on Nov 14, 2024. It is now read-only.

Commit 0aacffb

Browse files
nullable eager deprecation
1 parent 0ce0cf8 commit 0aacffb

File tree

1 file changed

+5
-85
lines changed

1 file changed

+5
-85
lines changed

_posts/2023-05-03-functional-error-handling-in-kotlin.md

Lines changed: 5 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,9 @@ The Arrow DSL applies the same syntax for all the types we can use to handle err
347347
For nullable type, Arrow offers the `nullable` DSL. We can access helpful functions inside the DSL, such as the `ensureNotNull` function and the `bind` extension function. Let's rewrite the `sumSalaries` function using the `nullable` DSL, adding a few logs that we'll use to understand what's going on:
348348

349349
```kotlin
350-
fun sumSalaries2(jobId1: JobId, jobId2: JobId): Double? = nullable.eager {
350+
import arrow.core.raise.*
351+
352+
fun sumSalaries2(jobId1: JobId, jobId2: JobId): Double? = nullable {
351353
println("Searching for the job with id $jobId1")
352354
val job1: Job = jobs.findById(jobId1).bind()
353355
println("Job found: $job1")
@@ -358,7 +360,7 @@ fun sumSalaries2(jobId1: JobId, jobId2: JobId): Double? = nullable.eager {
358360
}
359361
```
360362

361-
As we can see, the two functions extract the value from the nullable type. If the value is `null`, then the `nullable.eager` block returns `null` immediately. Here, we use the `eager` function, which accepts a not-suspendable function. We can use the `nullable` DSL directly to use a suspendable function (in the upcoming version 2.0 of Arrow, there will be only a single `nullable` DSL managing both cases).
363+
As we can see, the two functions extract the value from the nullable type. If the value is `null`, then the `nullable` block returns `null` immediately. In the version 2.0 of Arrow, the `nullable` DSL manages both normal and suspend functions inside.
362364

363365
We can give the function a job id that doesn't exist, and then we can check its behavior:
364366

@@ -381,88 +383,6 @@ The sum of the salaries using 'sumSalaries' is 0.0
381383

382384
As we might expect, the function returns `null` immediately after searching for the `JobId(42)` returns `null`.
383385

384-
Both the `nullable` and the `nullable.eager` DSL have a scope as the receiver, respectively an `arrow.core.continuations.NullableEagerEffectScope`and an `arrow.core.continuations.NullableEffectScope`:
385-
386-
```kotlin
387-
// Arrow SDK
388-
public object nullable {
389-
public inline fun <A> eager(crossinline f: suspend NullableEagerEffectScope.() -> A): A? =
390-
// Omissis
391-
392-
public suspend inline operator fun <A> invoke(crossinline f: suspend NullableEffectScope.() -> A): A? =
393-
// Omissis
394-
}
395-
```
396-
397-
The function `ensureNotNull` is an extension function defined on the scope `NullableEagerEffectScope` that calls the same function defined on the scope `EagerEffectScope`, from which it inherits:
398-
399-
```kotlin
400-
// Arrow SDK
401-
@OptIn(ExperimentalContracts::class)
402-
public suspend fun <B> NullableEffectScope.ensureNotNull(value: B?): B {
403-
contract { returns() implies (value != null) }
404-
return ensureNotNull(value) { null }
405-
}
406-
407-
@OptIn(ExperimentalContracts::class)
408-
public suspend fun <R, B : Any> EagerEffectScope<R>.ensureNotNull(value: B?, shift: () -> R): B {
409-
contract { returns() implies (value != null) }
410-
return value ?: shift(shift())
411-
}
412-
```
413-
414-
The `shift` function basically short-circuits the execution of the effect that calls it. In the case of the `NullableEffectScope` effect, returning `null` immediately.
415-
416-
417-
The `bind` is declared as a member extension function in the `NullableEagerEffectScope` class, making it available only inside the scope created using an object of type `NullableEagerEffectScope`:
418-
419-
```kotlin
420-
// Arrow SDK
421-
public value class NullableEagerEffectScope(/* Omissis */): EagerEffectScope<Nothing?> {
422-
423-
// Omissis...
424-
425-
public suspend fun <B> Option<B>.bind(): B =
426-
bind { null }
427-
428-
@OptIn(ExperimentalContracts::class)
429-
public suspend fun <B> B?.bind(): B {
430-
contract { returns() implies (this@bind != null) }
431-
return this ?: shift(null)
432-
}
433-
434-
// Omissis...
435-
}
436-
```
437-
438-
In Kotlin, we said that the `NullableEagerEffectScope` is the **dispatch receiver** of the `bind` function, whereas the `B?` type is the **extension receiver**. In reality, the `bind` function is just a wrapper to the same function defined in the `Option` type that we'll see in the next section.
439-
440-
Another useful member extension function of the `NullableEagerEffectScope` is the `ensure` function:
441-
442-
```kotlin
443-
// Arrow SDK
444-
public suspend fun ensure(value: Boolean): Unit = ensure(value) { null }
445-
```
446-
447-
The above function uses the more general `ensure` function defined in the `EagerEffectScope` class, which shift the value of the computation to a generic given value if the given condition is not satisfied:
448-
449-
```kotlin
450-
public suspend fun ensure(condition: Boolean, shift: () -> R): Unit =
451-
if (condition) Unit else shift(shift())
452-
```
453-
454-
In detail, the function verifies the given condition. It short-circuits the execution inside the scope to `null` if the condition is unmet. To give an example, we can change the function that converts USD to EUR, returning `null` if the given amount is negative:
455-
456-
```kotlin
457-
class CurrencyConverter {
458-
// Omissis...
459-
fun convertUsdToEurOrNull(amount: Double): Double? = nullable.eager {
460-
ensure(amount >= 0.0)
461-
amount * 0.91
462-
}
463-
}
464-
```
465-
466386
Although nullable types offer a reasonable degree of compositionality and full support by the Kotlin language, there are some cases when you can't use it to handle errors. There are some domains where the `null` value is valid, and we can't use it to represent an error. Other times, we need to work with some external libraries that don't support nullable types, such as RxJava or the project Reactor.
467387

468388
Fortunately, Kotlin and the Arrow library provide a lot of alternatives to handle errors functionally. Let's start with the `Option` type.
@@ -712,7 +632,7 @@ fun getSalaryGapWithMax3(jobId: JobId): Option<Double> = option.eager {
712632
Last but not least, the `nullable` DSL we've seen in the previous section integrates smoothly with the `Option` type. In this case, the `bind` function called on a `None` type will eagerly end the whole block returning a `null` value:
713633

714634
```kotlin
715-
fun getSalaryGapWithMax4(jobId: JobId): Double? = nullable.eager {
635+
fun getSalaryGapWithMax4(jobId: JobId): Double? = nullable {
716636
println("Searching for the job with id $jobId")
717637
val job: Job = jobs.findById(jobId).bind()
718638
println("Job found: $job")

0 commit comments

Comments
 (0)