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 bind alias for Sequence operation in DSL #2978

Merged
merged 12 commits into from
Mar 24, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import arrow.core.Either.Right
import arrow.core.raise.Raise
import arrow.core.raise.either
import arrow.core.raise.RaiseAccumulate
import arrow.core.raise.either
import arrow.core.raise.fold
import arrow.core.raise.mapOrAccumulate
import arrow.core.raise.nullable
import arrow.core.raise.option
Expand Down Expand Up @@ -324,14 +322,14 @@ public inline fun <E, A, B> Iterable<A>.traverse(f: (A) -> Either<E, B>): Either
ReplaceWith("let { l -> either<E, List<A>> { l.bind() } }", "arrow.core.raise.either")
)
public fun <E, A> Iterable<Either<E, A>>.sequenceEither(): Either<E, List<A>> =
let { l -> either { l.bind() } }
let { l -> either { l.bindAll() } }

@Deprecated(
"Sequence is being deprecated in favor of the either DSL.",
ReplaceWith("let { l -> either<E, List<A>> { l.bind() } }", "arrow.core.raise.either")
)
public fun <E, A> Iterable<Either<E, A>>.sequence(): Either<E, List<A>> =
let { l -> either { l.bind() } }
let { l -> either { l.bindAll() } }

@OptIn(ExperimentalTypeInference::class)
@OverloadResolutionByLambdaReturnType
Expand All @@ -357,14 +355,14 @@ public inline fun <A, B> Iterable<A>.traverseResult(f: (A) -> Result<B>): Result
ReplaceWith("let { l -> either<E, List<A>> { l.bind() } }", "arrow.core.raise.either")
)
public fun <A> Iterable<Result<A>>.sequenceResult(): Result<List<A>> =
let { l -> result { l.bind() } }
let { l -> result { l.bindAll() } }

@Deprecated(
"Sequence is being deprecated in favor of the result DSL.",
ReplaceWith("let { l -> result<List<A>> { l.bind() } }", "arrow.core.raise.result")
)
public fun <A> Iterable<Result<A>>.sequence(): Result<List<A>> =
let { l -> result { l.bind() } }
let { l -> result { l.bindAll() } }

@Deprecated(
ValidatedDeprMsg + "Use the mapOrAccumulate API instead",
Expand Down Expand Up @@ -481,14 +479,14 @@ public inline fun <A, B> Iterable<A>.traverse(f: (A) -> Option<B>): Option<List<
ReplaceWith("let { l -> result<List<A>> { l.bind() } }", "arrow.core.raise.result")
)
public fun <A> Iterable<Option<A>>.sequenceOption(): Option<List<A>> =
let { l -> option { l.bind() } }
let { l -> option { l.bindAll() } }

@Deprecated(
"Sequence is being deprecated in favor of the option DSL.",
ReplaceWith("let { l -> option<List<A>> { l.bind() } }", "arrow.core.raise.option")
)
public fun <A> Iterable<Option<A>>.sequence(): Option<List<A>> =
let { l -> option { l.bind() } }
let { l -> option { l.bindAll() } }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the let is unnecessary here, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

option { this@sequence.bindAll() }

Copy link
Member Author

@nomisRev nomisRev Mar 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, indeed it's so that the ReplaceWith is inline with the implementation body if people click through. Without let ReplaceWith horribly suffers from scope pollution 😞

let should incur any perf penalty due to @InlineOnly.


@Deprecated(
"traverseNullable is being renamed to traverse to simplify the Arrow API",
Expand Down Expand Up @@ -517,14 +515,14 @@ public inline fun <A, B> Iterable<A>.traverse(f: (A) -> B?): List<B>? {
ReplaceWith("let { l -> nullable<List<A>> { l.bind() } }", "arrow.core.raise.nullable")
)
public fun <A> Iterable<A?>.sequenceNullable(): List<A>? =
let { l -> nullable { l.bind() } }
let { l -> nullable { l.bindAll() } }

@Deprecated(
"Sequence is being deprecated in favor of the nullable DSL.",
ReplaceWith("let { l -> nullable<List<A>> { l.bind() } }", "arrow.core.raise.nullable")
)
public fun <A> Iterable<A?>.sequence(): List<A>? =
let { l -> nullable { l.bind() } }
let { l -> nullable { l.bindAll() } }

/**
* Returns [Either] a [List] containing the results of applying the given [transform] function to each element in the original collection,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,9 @@ import arrow.core.Option
import arrow.core.Some
import arrow.core.getOrElse
import arrow.core.identity
import arrow.core.orElse
import arrow.typeclasses.Semigroup
import arrow.typeclasses.SemigroupDeprecation
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
import kotlin.contracts.contract
import kotlin.experimental.ExperimentalTypeInference
import kotlin.jvm.JvmInline
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName

Expand Down Expand Up @@ -66,7 +61,7 @@ public class NullableRaise(private val cont: Raise<Null>) : Raise<Null> {

@RaiseDSL
@JvmName("bindAllNullable")
public fun <A> Iterable<A?>.bind(): List<A> =
public fun <A> Iterable<A?>.bindAll(): List<A> =
map { it.bind() }

@RaiseDSL
Expand All @@ -85,7 +80,7 @@ public class ResultRaise(private val cont: Raise<Throwable>) : Raise<Throwable>

@RaiseDSL
@JvmName("bindAllResult")
public fun <A> Iterable<Result<A>>.bind(): List<A> =
public fun <A> Iterable<Result<A>>.bindAll(): List<A> =
map { it.bind() }
}

Expand All @@ -99,7 +94,7 @@ public class OptionRaise(private val cont: Raise<None>) : Raise<None> {

@RaiseDSL
@JvmName("bindAllOption")
public fun <A> Iterable<Option<A>>.bind(): List<A> =
public fun <A> Iterable<Option<A>>.bindAll(): List<A> =
map { it.bind() }

@RaiseDSL
Expand All @@ -121,6 +116,11 @@ public class IorRaise<E> @PublishedApi internal constructor(
@RaiseDSL
override fun raise(r: E): Nothing = raise.raise(combine(r))

@RaiseDSL
@JvmName("bindAllIor")
public fun <A> Iterable<Ior<E, A>>.bindAll(): List<A> =
map { it.bind() }

@RaiseDSL
public fun <B> Ior<E, B>.bind(): B =
when (this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import arrow.core.recover
import kotlin.coroutines.cancellation.CancellationException
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind.AT_MOST_ONCE
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
import kotlin.contracts.contract
import kotlin.experimental.ExperimentalTypeInference
import kotlin.jvm.JvmMultifileClass
Expand Down Expand Up @@ -172,7 +171,7 @@ public interface Raise<in R> {
/**
* Extract the [Either.Right] value of an [Either].
* Any encountered [Either.Left] will be raised as a _logical failure_ in `this` [Raise] context.
* You can wrap the [bind] call in [recover] if you want to attempt to recover from any _logical failure_.
* You can wrap the [bindAll] call in [recover] if you want to attempt to recover from any _logical failure_.
*
* <!--- INCLUDE
* import arrow.core.Either
Expand Down Expand Up @@ -211,7 +210,7 @@ public interface Raise<in R> {
}

@RaiseDSL
public fun <A> Iterable<Either<R, A>>.bind(): List<A> =
public fun <A> Iterable<Either<R, A>>.bindAll(): List<A> =
map { it.bind() }

@RaiseDSL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
@file:JvmName("RaiseKt")
package arrow.core.raise

import arrow.core.mapOrAccumulate
import arrow.core.Either
import arrow.core.EitherNel
import arrow.core.EmptyValue
Expand Down Expand Up @@ -491,6 +490,15 @@ public open class RaiseAccumulate<Error>(
public override fun raise(r: Error): Nothing =
raise.raise(nonEmptyListOf(r))

@RaiseDSL
public inline fun <A, B> Iterable<A>.mapOrAccumulate(
transform: RaiseAccumulate<Error>.(A) -> B
): List<B> = raise.mapOrAccumulate(this) { transform(it) }

@RaiseDSL
override fun <A> Iterable<Either<Error, A>>.bindAll(): List<A> =
mapOrAccumulate { it.bind() }

@RaiseDSL
public fun <A> EitherNel<Error, A>.bindNel(): A = when (this) {
is Either.Left -> raise.raise(value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import arrow.core.NonEmptyList
import arrow.core.identity
import arrow.core.left
import arrow.core.right
import arrow.core.test.either
import arrow.core.toNonEmptyListOrNull
import io.kotest.assertions.fail
import io.kotest.assertions.throwables.shouldThrow
Expand Down Expand Up @@ -615,6 +616,31 @@ class EffectSpec : StringSpec({
}
}

"bindAll fails on first error" {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are the two tests that show the behavior @serras

checkAll(Arb.list(Arb.either(Arb.string(), Arb.int()))) { eithers ->
val expected = eithers.firstOrNull { it.isLeft() } ?: eithers.mapNotNull { it.getOrNull() }.right()
either {
eithers.bindAll()
} shouldBe expected
}
}

fun <E, A> Either<E, A>.leftOrNull(): E? = fold(::identity) { null }

"accumulate - bindAll" {
checkAll(Arb.list(Arb.either(Arb.string(), Arb.int()))) { eithers ->
val expected =
eithers.mapNotNull { it.leftOrNull() }.toNonEmptyListOrNull()?.left() ?: eithers.mapNotNull { it.getOrNull() }.right()

either<NonEmptyList<String>, List<Int>> {
zipOrAccumulate(
{ eithers.bindAll() },
{ emptyList<Int>() }
) { a, b -> a + b }
} shouldBe expected
}
}

"shift leaked results in RaiseLeakException" {
effect {
suspend { raise("failure") }
Expand Down