Skip to content

Commit

Permalink
Adds traverse for Result on a List (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
cwmyers authored Oct 4, 2024
1 parent 963d443 commit e8d1a46
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 1 deletion.
2 changes: 2 additions & 0 deletions lib/api/lib.api
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,10 @@ public final class app/cash/quiver/extensions/IorKt {
public final class app/cash/quiver/extensions/IterableKt {
public static final fun traverse (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Larrow/core/Either;
public static final fun traverse (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Larrow/core/Option;
public static final fun traverse (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun traverseEither (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Larrow/core/Either;
public static final fun traverseOption (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Larrow/core/Option;
public static final fun traverseResult (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
}

public final class app/cash/quiver/extensions/ListKt {
Expand Down
17 changes: 16 additions & 1 deletion lib/src/main/kotlin/app/cash/quiver/extensions/Iterable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.raise.either
import arrow.core.raise.result
import kotlin.experimental.ExperimentalTypeInference

/**
Expand All @@ -16,12 +17,27 @@ import kotlin.experimental.ExperimentalTypeInference
inline fun <E, A, B> Iterable<A>.traverse(f: (A) -> Either<E, B>): Either<E, List<B>> =
let { l -> either { l.map { f(it).bind() } } }

/**
* Returns a Result of a list of B results of applying the given transform function
* to each element(A) in the original collection.
*/
@OptIn(ExperimentalTypeInference::class)
@OverloadResolutionByLambdaReturnType
inline fun <A, B> Iterable<A>.traverse(f: (A) -> Result<B>): Result<List<B>> =
let { l -> result { l.map { f(it).bind() } } }

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

/**
* Synonym for traverse((A)-> Result<B>): Result<List<B>>
*/
inline fun <A, B> Iterable<A>.traverseResult(f: (A) -> Result<B>): Result<List<B>> =
traverse(f)

/**
* Returns an Option of a list of B results of applying the given transform function
* to each element(A) in the original collection.
Expand All @@ -43,4 +59,3 @@ inline fun <A, B> Iterable<A>.traverse(f: (A) -> Option<B>): Option<List<B>> {
*/
inline fun <A, B> Iterable<A>.traverseOption(f: (A) -> Option<B>): Option<List<B>> =
traverse(f)

19 changes: 19 additions & 0 deletions lib/src/test/kotlin/app/cash/quiver/extensions/TraverseTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import io.kotest.assertions.arrow.core.shouldBeLeft
import io.kotest.assertions.arrow.core.shouldBeRight
import io.kotest.assertions.arrow.core.shouldBeSome
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.result.shouldBeFailure
import io.kotest.matchers.result.shouldBeSuccess
import io.kotest.matchers.shouldBe

class TraverseTest : StringSpec({
Expand All @@ -25,6 +27,11 @@ class TraverseTest : StringSpec({
result shouldBeRight emptyList()
}

"traverse an empty list returns a Success of an empty list" {
val result = emptyList<Int>().traverse { Result.success(it) }
result shouldBeSuccess emptyList()
}

"traverse a list of integers returns a left of an error" {
val result = listOf(1, 2, 3).traverse { Either.Left("error") }
result shouldBeLeft "error"
Expand All @@ -37,11 +44,23 @@ class TraverseTest : StringSpec({
result shouldBeLeft 4
}

"the failure returned is the first failure returned by the function as it maps over the iterable" {
val result = listOf(1, 2, 4, 6, 7).traverse {
if (it < 3) Result.success(it) else Result.failure(Throwable(message = it.toString()))
}
result.shouldBeFailure().message shouldBe "4"
}

"traverseEither a list of integers returns a Right of the list of mapped strings" {
val result = listOf(1, 2, 3).traverseEither { Either.Right(it.toString()) }
result shouldBeRight listOf("1", "2", "3")
}

"traverseResult a list of integers returns a Success of the list of mapped strings" {
val result = listOf(1, 2, 3).traverseResult { Result.success(it.toString()) }
result shouldBeSuccess listOf("1", "2", "3")
}

"traverse a list of integers returns a Some of the list of mapped strings" {
val result = listOf(1, 2, 3).traverse { Some(it.toString()) }
result shouldBeSome listOf("1", "2", "3")
Expand Down

0 comments on commit e8d1a46

Please sign in to comment.