Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Nullable proposal #2675

Merged
merged 12 commits into from Mar 2, 2022
75 changes: 75 additions & 0 deletions arrow-libs/core/arrow-core/api/arrow-core.api
Expand Up @@ -2681,6 +2681,75 @@ public final class arrow/core/continuations/IorEffectScope : arrow/core/continua
public fun shift (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class arrow/core/continuations/NullableEagerEffectScope : arrow/core/continuations/EagerEffectScope {
public fun bind (Larrow/core/Either;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Larrow/core/Option;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Larrow/core/Validated;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Larrow/core/continuations/EagerEffect;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun bind-impl (Larrow/core/continuations/EagerEffectScope;Larrow/core/Either;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun bind-impl (Larrow/core/continuations/EagerEffectScope;Larrow/core/Option;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun bind-impl (Larrow/core/continuations/EagerEffectScope;Larrow/core/Option;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun bind-impl (Larrow/core/continuations/EagerEffectScope;Larrow/core/Validated;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun bind-impl (Larrow/core/continuations/EagerEffectScope;Larrow/core/continuations/EagerEffect;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun bind-impl (Larrow/core/continuations/EagerEffectScope;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun bind-impl (Larrow/core/continuations/EagerEffectScope;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final synthetic fun box-impl (Larrow/core/continuations/EagerEffectScope;)Larrow/core/continuations/NullableEagerEffectScope;
public static fun constructor-impl (Larrow/core/continuations/EagerEffectScope;)Larrow/core/continuations/EagerEffectScope;
public fun ensure (ZLkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun ensure-impl (Larrow/core/continuations/EagerEffectScope;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun ensure-impl (Larrow/core/continuations/EagerEffectScope;ZLkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Larrow/core/continuations/EagerEffectScope;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Larrow/core/continuations/EagerEffectScope;Larrow/core/continuations/EagerEffectScope;)Z
public fun hashCode ()I
public static fun hashCode-impl (Larrow/core/continuations/EagerEffectScope;)I
public synthetic fun shift (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun shift (Ljava/lang/Void;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun shift-impl (Larrow/core/continuations/EagerEffectScope;Ljava/lang/Void;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Larrow/core/continuations/EagerEffectScope;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Larrow/core/continuations/EagerEffectScope;
}

public final class arrow/core/continuations/NullableEffectScope : arrow/core/continuations/EffectScope {
public fun bind (Larrow/core/Either;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Larrow/core/Option;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Larrow/core/Validated;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Larrow/core/continuations/EagerEffect;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Larrow/core/continuations/Effect;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun bind-impl (Larrow/core/continuations/EffectScope;Larrow/core/Either;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun bind-impl (Larrow/core/continuations/EffectScope;Larrow/core/Option;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun bind-impl (Larrow/core/continuations/EffectScope;Larrow/core/Option;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun bind-impl (Larrow/core/continuations/EffectScope;Larrow/core/Validated;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun bind-impl (Larrow/core/continuations/EffectScope;Larrow/core/continuations/EagerEffect;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun bind-impl (Larrow/core/continuations/EffectScope;Larrow/core/continuations/Effect;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun bind-impl (Larrow/core/continuations/EffectScope;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun bind-impl (Larrow/core/continuations/EffectScope;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final synthetic fun box-impl (Larrow/core/continuations/EffectScope;)Larrow/core/continuations/NullableEffectScope;
public static fun constructor-impl (Larrow/core/continuations/EffectScope;)Larrow/core/continuations/EffectScope;
public fun ensure (ZLkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun ensure-impl (Larrow/core/continuations/EffectScope;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun ensure-impl (Larrow/core/continuations/EffectScope;ZLkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Larrow/core/continuations/EffectScope;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Larrow/core/continuations/EffectScope;Larrow/core/continuations/EffectScope;)Z
public fun hashCode ()I
public static fun hashCode-impl (Larrow/core/continuations/EffectScope;)I
public synthetic fun shift (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun shift (Ljava/lang/Void;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun shift-impl (Larrow/core/continuations/EffectScope;Ljava/lang/Void;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Larrow/core/continuations/EffectScope;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Larrow/core/continuations/EffectScope;
}

public final class arrow/core/continuations/NullableKt {
public static final fun ensureNotNull-0Rsnnio (Larrow/core/continuations/EffectScope;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun ensureNotNull-7s8y1X8 (Larrow/core/continuations/EagerEffectScope;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class arrow/core/continuations/OptionEagerEffectScope : arrow/core/continuations/EagerEffectScope {
public fun bind (Larrow/core/Either;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun bind (Larrow/core/Option;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand Down Expand Up @@ -2843,6 +2912,12 @@ public final class arrow/core/continuations/ior {
public final fun invoke (Larrow/typeclasses/Semigroup;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class arrow/core/continuations/nullable {
public static final field INSTANCE Larrow/core/continuations/nullable;
public final fun eager (Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
public final fun invoke (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class arrow/core/continuations/option {
public static final field INSTANCE Larrow/core/continuations/option;
public final fun eager (Lkotlin/jvm/functions/Function2;)Larrow/core/Option;
Expand Down
Expand Up @@ -7,6 +7,7 @@ import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.coroutines.RestrictsSuspension

@Deprecated(deprecateInFavorOfEffectScope)
public fun interface NullableEffect<A> : Effect<A?> {
public suspend fun <B> B?.bind(): B =
this ?: control().shift(null)
Expand Down Expand Up @@ -67,6 +68,7 @@ public fun interface NullableEffect<A> : Effect<A?> {
* ```
* <!--- KNIT example-nullable-computations-02.kt -->
*/
@Deprecated(deprecateInFavorOfEffectScope, ReplaceWith("ensureNotNull", "arrow.core.continuations.ensureNotNull"))
@OptIn(ExperimentalContracts::class) // Contracts not available on open functions, so made it top-level.
public suspend fun <B : Any> NullableEffect<*>.ensureNotNull(value: B?): B {
contract {
Expand All @@ -76,14 +78,18 @@ public suspend fun <B : Any> NullableEffect<*>.ensureNotNull(value: B?): B {
return value ?: control().shift(null)
}

@Deprecated(deprecatedInFavorOfEagerEffectScope)
@RestrictsSuspension
public fun interface RestrictedNullableEffect<A> : NullableEffect<A>

@Deprecated(deprecateInFavorOfEffectOrEagerEffect, ReplaceWith("nullable", "arrow.core.continuations.nullable"))
@Suppress("ClassName")
public object nullable {
@Deprecated(deprecateInFavorOfEagerEffect, ReplaceWith("nullable.eager(func)", "arrow.core.continuations.nullable"))
public inline fun <A> eager(crossinline func: suspend RestrictedNullableEffect<A>.() -> A?): A? =
Effect.restricted(eff = { RestrictedNullableEffect { it } }, f = func, just = { it })

@Deprecated(deprecateInFavorOfEffect, ReplaceWith("nullable(func)", "arrow.core.continuations.nullable"))
public suspend inline operator fun <A> invoke(crossinline func: suspend NullableEffect<*>.() -> A?): A? =
Effect.suspended(eff = { NullableEffect { it } }, f = func, just = { it })
}
@@ -0,0 +1,67 @@
package arrow.core.continuations

import arrow.core.Option
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.jvm.JvmInline

@JvmInline
public value class NullableEffectScope(private val cont: EffectScope<Nothing?>) : EffectScope<Nothing?> {
override suspend fun <B> shift(r: Nothing?): B =
cont.shift(r)

public suspend fun <B> Option<B>.bind(): B =
bind { null }

@OptIn(ExperimentalContracts::class)
public suspend fun <B> B?.bind(): B {
contract { returns() implies (this@bind != null) }
return this ?: shift(null)
}

public suspend fun ensure(value: Boolean): Unit =
ensure(value) { null }
}

@JvmInline
public value class NullableEagerEffectScope(private val cont: EagerEffectScope<Nothing?>) : EagerEffectScope<Nothing?> {
@Suppress("ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL")
override suspend fun <B> shift(r: Nothing?): B =
cont.shift(r)

public suspend fun <B> Option<B>.bind(): B =
bind { null }

@OptIn(ExperimentalContracts::class)
public suspend fun <B> B?.bind(): B {
contract { returns() implies (this@bind != null) }
return this ?: shift(null)
}

public suspend fun ensure(value: Boolean): Unit =
ensure(value) { null }
}

@OptIn(ExperimentalContracts::class)
public suspend fun <B> NullableEffectScope.ensureNotNull(value: B?): B {
contract { returns() implies (value != null) }
return ensureNotNull(value) { null }
}

@OptIn(ExperimentalContracts::class)
public suspend fun <B> NullableEagerEffectScope.ensureNotNull(value: B?): B {
contract { returns() implies (value != null) }
return ensureNotNull(value) { null }
}

@Suppress("ClassName")
public object nullable {
public inline fun <A> eager(crossinline f: suspend NullableEagerEffectScope.() -> A): A? =
eagerEffect<Nothing?, A> {
@Suppress("ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL")
f(NullableEagerEffectScope(this))
}.orNull()

public suspend inline operator fun <A> invoke(crossinline f: suspend NullableEffectScope.() -> A): A? =
effect<Nothing?, A> { f(NullableEffectScope(this)) }.orNull()
}
@@ -0,0 +1,122 @@
package arrow.core.continuations

import arrow.core.Either
import arrow.core.Some
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.kotest.property.Arb
import io.kotest.property.arbitrary.boolean
import io.kotest.property.arbitrary.int
import io.kotest.property.arbitrary.orNull
import io.kotest.property.checkAll

class NullableSpec : StringSpec({
"ensure null in nullable computation" {
checkAll(Arb.boolean(), Arb.int()) { predicate, i ->
nullable {
ensure(predicate)
i
} shouldBe if (predicate) i else null
}
}

"ensureNotNull in nullable computation" {
fun square(i: Int): Int = i * i
checkAll(Arb.int().orNull()) { i: Int? ->
nullable {
val ii = i
ensureNotNull(ii)
square(ii) // Smart-cast by contract
} shouldBe i?.let(::square)
}
}

"short circuit null" {
nullable {
val number: Int = "s".length
val x = ensureNotNull(number.takeIf { it > 1 })
x
throw IllegalStateException("This should not be executed")
} shouldBe null
}

"simple case" {
nullable {
"s".length.bind()
} shouldBe 1
}

"multiple types" {
nullable {
val number = "s".length
val string = number.toString().bind()
string
} shouldBe "1"
}

"binding option in nullable" {
nullable {
val number = Some("s".length)
val string = number.map(Int::toString).bind()
string
} shouldBe "1"
}

"short circuit null" {
nullable {
val number: Int = "s".length
(number.takeIf { it > 1 }?.toString()).bind()
throw IllegalStateException("This should not be executed")
} shouldBe null
}

"short circuit option" {
nullable {
val number = Some("s".length)
number.filter { it > 1 }.map(Int::toString).bind()
throw IllegalStateException("This should not be executed")
} shouldBe null
}

"when expression" {
nullable {
val number = "s".length.bind()
val string = when (number) {
1 -> number.toString()
else -> null
}.bind()
string
} shouldBe "1"
}

"if expression" {
nullable {
val number = "s".length.bind()
val string = if (number == 1) {
number.toString()
} else {
null
}.bind()
string
} shouldBe "1"
}

"if expression short circuit" {
nullable {
val number = "s".length.bind()
val string = if (number != 1) {
number.toString()
} else {
null
}.bind()
string
} shouldBe null
i-walker marked this conversation as resolved.
Show resolved Hide resolved
}

"Either<Nothing, A> short circuit" {
i-walker marked this conversation as resolved.
Show resolved Hide resolved
nullable {
val either: Either<Nothing, Int> = Either.Right(4)
either.bind() + 3
} shouldBe 7
}
})