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

backport Arrow deprecated traverse functions #70

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Change Log

## [Unreleased]
* Backport traverse functions on Either, Iterable and Option (Andrew Parker)
* Backport traverse functions on Sequence, Map and Ior (Andrew Parker)

## [0.5.0] - 2023-08-26

Expand Down
1,288 changes: 1,288 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions lib/api/lib.api
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ public final class app/cash/quiver/extensions/EitherKt {
public final class app/cash/quiver/extensions/IorKt {
public static final fun emptyCombine (Larrow/core/Option;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
public static final fun getUnitIor ()Larrow/core/Ior;
public static final fun traverse (Larrow/core/Ior;Lkotlin/jvm/functions/Function1;)Larrow/core/Either;
public static final fun traverse (Larrow/core/Ior;Lkotlin/jvm/functions/Function1;)Larrow/core/Option;
public static final fun traverse (Larrow/core/Ior;Lkotlin/jvm/functions/Function1;)Ljava/util/List;
public static final fun traverseEither (Larrow/core/Ior;Lkotlin/jvm/functions/Function1;)Larrow/core/Either;
public static final fun traverseOption (Larrow/core/Ior;Lkotlin/jvm/functions/Function1;)Larrow/core/Option;
public static final fun zip (Larrow/core/Ior;Lkotlin/jvm/functions/Function2;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Lkotlin/jvm/functions/Function10;)Larrow/core/Ior;
public static final fun zip (Larrow/core/Ior;Lkotlin/jvm/functions/Function2;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Lkotlin/jvm/functions/Function9;)Larrow/core/Ior;
public static final fun zip (Larrow/core/Ior;Lkotlin/jvm/functions/Function2;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Larrow/core/Ior;Lkotlin/jvm/functions/Function8;)Larrow/core/Ior;
Expand All @@ -220,6 +225,10 @@ public final class app/cash/quiver/extensions/ListKt {

public final class app/cash/quiver/extensions/MapKt {
public static final fun getOption (Ljava/util/Map;Ljava/lang/Object;)Larrow/core/Option;
public static final fun traverse (Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Larrow/core/Either;
public static final fun traverse (Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Larrow/core/Option;
public static final fun traverseEither (Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Larrow/core/Either;
public static final fun traverseOption (Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Larrow/core/Option;
}

public final class app/cash/quiver/extensions/NonEmptyListKt {
Expand Down Expand Up @@ -260,6 +269,13 @@ public final class app/cash/quiver/extensions/ResultKt {
public static final fun toEither (Ljava/lang/Object;)Larrow/core/Either;
}

public final class app/cash/quiver/extensions/SequenceKt {
public static final fun traverse (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Larrow/core/Either;
public static final fun traverse (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Larrow/core/Option;
public static final fun traverseEither (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Larrow/core/Either;
public static final fun traverseOption (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Larrow/core/Option;
}

public final class app/cash/quiver/extensions/SuspendedFunctionKt {
public static final fun map (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
public static final fun map (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function2;
Expand Down
44 changes: 42 additions & 2 deletions lib/src/main/kotlin/app/cash/quiver/extensions/Ior.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package app.cash.quiver.extensions

import arrow.core.Either
import arrow.core.Ior
import arrow.core.Ior.Both
import arrow.core.Ior.Left
import arrow.core.Ior.Right
import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.some
import kotlin.experimental.ExperimentalTypeInference
import app.cash.quiver.extensions.traverse as quiverTraverse

@PublishedApi
internal val unitIor: Ior<Nothing, Unit> = Ior.Right(Unit)
internal val unitIor: Ior<Nothing, Unit> = Right(Unit)

inline fun <A, B, C, D> Ior<A, B>.zip(
crossinline combine: (A, A) -> A,
Expand Down Expand Up @@ -253,7 +257,7 @@ inline fun <A, B, C, D, E, F, G, H, I, J, K, L> Ior<A, B>.zip(
return when(right) {
is Some -> when(left) {
is Some -> Both(left.value, right.value)
is None -> Ior.Right(right.value)
is None -> Right(right.value)
}
None -> when(left) {
is Some -> Left(left.value)
Expand All @@ -268,3 +272,39 @@ internal inline fun <A> Option<A>.emptyCombine(other: A, combine: (A, A) -> A):
is Some -> combine(this.value, other)
is None -> other
}

@OptIn(ExperimentalTypeInference::class)
@OverloadResolutionByLambdaReturnType
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <A, B, AA, C> Ior<A, B>.traverse(f: (B) -> Either<AA, C>): Either<AA, Ior<A, C>> =
fold(
{ a -> Either.Right(Left(a)) },
{ b -> f(b).map { Right(it) } },
{ a, b -> f(b).map { Both(a, it) } }
)

@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <A, B, AA, C> Ior<A, B>.traverseEither(f: (B) -> Either<AA, C>): Either<AA, Ior<A, C>> =
quiverTraverse(f)

@OptIn(ExperimentalTypeInference::class)
@OverloadResolutionByLambdaReturnType
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <A, B, C> Ior<A, B>.traverse(f: (B) -> Option<C>): Option<Ior<A, C>> =
fold(
{ a -> Some(Left(a)) },
{ b -> f(b).map { Right(it) } },
{ a, b -> f(b).map { Both(a, it) } }
)

@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <A, B, C> Ior<A, B>.traverseOption(f: (B) -> Option<C>): Option<Ior<A, C>> =
quiverTraverse(f)

@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <A, B, C> Ior<A, B>.traverse(f: (B) -> Iterable<C>): List<Ior<A, C>> =
fold(
{ a -> listOf(Left(a)) },
{ b -> f(b).map { Right(it) } },
{ a, b -> f(b).map { Both(a, it) } }
)
55 changes: 55 additions & 0 deletions lib/src/main/kotlin/app/cash/quiver/extensions/Map.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,65 @@
package app.cash.quiver.extensions

import arrow.core.Either
import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.getOrNone
import arrow.core.right
import arrow.core.some
import kotlin.experimental.ExperimentalTypeInference
import app.cash.quiver.extensions.traverse as quiverTraverse

/**
* Extension function to get an Option from a nullable object on a map.
*/
fun <K, A> Map<K, A>.getOption(k: K): Option<A> =
getOrNone(k)

/**
* Map a function that returns an Either over all the values in the Map.
* If the function returns a Right, the value is added to the resulting Map.
* If the function returns a Left, the result is the Left.
*/
@OptIn(ExperimentalTypeInference::class)
@OverloadResolutionByLambdaReturnType
inline fun <K, E, A, B> Map<K, A>.traverse(f: (A) -> Either<E, B>): Either<E, Map<K, B>> {
val acc = mutableMapOf<K, B>()
forEach { (k, v) ->
when (val res = f(v)) {
is Either.Right -> acc[k] = res.value
is Either.Left -> return@traverse res
}
}
return acc.right()
}

/**
* Synonym for traverse((A)-> Either<E, B>): Either<E, Map<K, B>>
*/
inline fun <K, E, A, B> Map<K, A>.traverseEither(f: (A) -> Either<E, B>): Either<E, Map<K, B>> =
quiverTraverse(f)

/**
* Map a function that returns an Option over all the values in the Map.
* If the function returns a Some, the value is added to the resulting Map.
* If the function returns a None, the result is None.
*/
@OptIn(ExperimentalTypeInference::class)
@OverloadResolutionByLambdaReturnType
inline fun <K, A, B> Map<K, A>.traverse(f: (A) -> Option<B>): Option<Map<K, B>> {
val acc = mutableMapOf<K, B>()
forEach { (k, v) ->
when (val res = f(v)) {
is Some -> acc[k] = res.value
is None -> return@traverse res
}
}
return acc.some()
}

/**
* Synonym for traverse((A)-> Option<B>): Option<Map<K, B>>
*/
inline fun <K, A, B> Map<K, A>.traverseOption(f: (A) -> Option<B>): Option<Map<K, B>> =
quiverTraverse(f)
67 changes: 67 additions & 0 deletions lib/src/main/kotlin/app/cash/quiver/extensions/Sequence.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
@file:Suppress("TYPEALIAS_EXPANSION_DEPRECATION", "DEPRECATION")

package app.cash.quiver.extensions

import arrow.core.Either
import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import kotlin.experimental.ExperimentalTypeInference
import app.cash.quiver.extensions.traverse as quiverTraverse

/**
* Map a function that returns an Either across the Sequence.
*/
@OptIn(ExperimentalTypeInference::class)
@OverloadResolutionByLambdaReturnType
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <A, E, B> Sequence<A>.traverse(f: (A) -> Either<E, B>): Either<E, List<B>> {
// Note: Using a mutable list here avoids the stackoverflows one can accidentally create when using
// Sequence.plus instead. But we don't convert the sequence to a list beforehand to avoid
// forcing too much of the sequence to be evaluated.
val result = mutableListOf<B>()
forEach { a ->
when (val mapped = f(a)) {
is Either.Right -> result.add(mapped.value)
is Either.Left -> return@traverse mapped
}
}
return Either.Right(result.toList())
}

/**
* Synonym for traverse((A)-> Either<E, B>): Either<E, List<B>>
*/
@OptIn(ExperimentalTypeInference::class)
@OverloadResolutionByLambdaReturnType
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <A, E, B> Sequence<A>.traverseEither(f: (A) -> Either<E, B>): Either<E, List<B>> =
quiverTraverse(f)

/**
* Map a function that returns an Option across the Sequence.
*/
@OptIn(ExperimentalTypeInference::class)
@OverloadResolutionByLambdaReturnType
inline fun <A, B> Sequence<A>.traverse(f: (A) -> Option<B>): Option<List<B>> {
// Note: Using a mutable list here avoids the stackoverflows one can accidentally create when using
// Sequence.plus instead. But we don't convert the sequence to a list beforehand to avoid
// forcing too much of the sequence to be evaluated.
val result = mutableListOf<B>()
forEach { a ->
when (val mapped = f(a)) {
is Some -> result.add(mapped.value)
is None -> return@traverse None
}
}
return Some(result.toList())
}

/**
* Synonym for traverse((A)-> Option<B>): Option<List<B>>
*/
@OptIn(ExperimentalTypeInference::class)
@OverloadResolutionByLambdaReturnType
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <A, B> Sequence<A>.traverseOption(f: (A) -> Option<B>): Option<List<B>> =
quiverTraverse(f)
72 changes: 70 additions & 2 deletions lib/src/test/kotlin/app/cash/quiver/extensions/IorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ package app.cash.quiver.extensions

import app.cash.quiver.arb.ior
import arrow.core.Ior
import arrow.core.leftIor
import arrow.core.rightIor
import arrow.core.None
import arrow.core.left
import arrow.core.right
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.int
import io.kotest.property.checkAll
import app.cash.quiver.extensions.traverse as quiverTraverse
import app.cash.quiver.extensions.traverseEither as quiverTraverseEither
import app.cash.quiver.extensions.traverseOption as quiverTraverseOption

class IorTest : StringSpec({
"zip10" {
Expand Down Expand Up @@ -36,6 +41,7 @@ class IorTest : StringSpec({
{ Ior.Right(acc.value + it) },
{ l, r -> Ior.Both(l, acc.value + r) }
)

is Ior.Both -> curr.fold(
{ Ior.Left(acc.leftValue + it) },
{ Ior.Both(acc.leftValue, it + acc.rightValue) },
Expand All @@ -47,4 +53,66 @@ class IorTest : StringSpec({
res shouldBe expected
}
}

"traverse Either on an Left returns an Either.Right of the Left" {
Ior.Left(1).quiverTraverse { "A".right() } shouldBe Ior.Left(1).right()
}

"traverse Either on a Right returns an Either.Right of the Right" {
Ior.Right(1).quiverTraverse { "A".right() } shouldBe Ior.Right("A").right()
}

"traverse Either on a Both returns an Either.Right of the Both with mapped Right" {
Ior.Both(1, 2).quiverTraverse { "A".right() } shouldBe Ior.Both(1, "A").right()
}

"traverseEither synonym for traverse Either" {
Ior.Both(1, 2).quiverTraverseEither { "A".right() } shouldBe Ior.Both(1, "A").right()
}

"traverse Either on a Right returns an Either.Left if the function returns an Either.Left" {
Ior.Right(1).quiverTraverse { "A".left() } shouldBe "A".left()
}

"traverse Either on a Both returns an Either.Left if the function returns an Either.Left" {
Ior.Both(1, 2).quiverTraverse { "A".left() } shouldBe "A".left()
}

"traverse Option on an Left returns an Some of the Left" {
Ior.Left(1).quiverTraverse { "A".some() } shouldBe Ior.Left(1).some()
}

"traverse Option on a Right returns an Some of the Right" {
Ior.Right(1).quiverTraverse { "A".some() } shouldBe Ior.Right("A").some()
}

"traverse Option on a Both returns an Some of the Both with mapped Right" {
Ior.Both(1, 2).quiverTraverse { "A".some() } shouldBe Ior.Both(1, "A").some()
}

"traverseOption synonym for traverse Option" {
Ior.Both(1, 2).quiverTraverseOption { "A".some() } shouldBe Ior.Both(1, "A").some()
}

"traverse Option on a Right returns a None if the function returns a None" {
Ior.Right(1).quiverTraverse { None } shouldBe None
}

"traverse Option on a Both returns a None if the function returns a None" {
Ior.Both(1, 2).quiverTraverse { None } shouldBe None
}


"traverse Iterable on an Left returns a List of the Left" {
Ior.Left(1).quiverTraverse { listOf("A", "B") } shouldBe listOf(Ior.Left(1))
}

"traverse Iterable on a Right returns a List of the Right" {
Ior.Right(1).quiverTraverse { listOf("A", "B") } shouldBe listOf(Ior.Right("A"), Ior.Right("B"))
}

"traverse Iterable on a Both returns a List of Boths with mapped Rights" {
Ior.Both(1, 2).quiverTraverse { listOf("A", "B") } shouldBe
listOf(Ior.Both(1, "A"), Ior.Both(1, "B"))
}
})
Loading
Loading