Skip to content

Commit

Permalink
Provide mapOrAccumulate for NonEmptySet (#3489)
Browse files Browse the repository at this point in the history
  • Loading branch information
tPl0ch authored Oct 3, 2024
1 parent e855ff9 commit d6073dd
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 1 deletion.
2 changes: 2 additions & 0 deletions arrow-libs/core/arrow-core/api/arrow-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,8 @@ public final class arrow/core/NonEmptySet : arrow/core/NonEmptyCollection, java/
}

public final class arrow/core/NonEmptySetKt {
public static final fun mapOrAccumulate-EyVDDLY (Ljava/util/Set;Lkotlin/jvm/functions/Function2;)Larrow/core/Either;
public static final fun mapOrAccumulate-jkbboic (Ljava/util/Set;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Larrow/core/Either;
public static final fun nonEmptySetOf (Ljava/lang/Object;[Ljava/lang/Object;)Ljava/util/Set;
public static final fun toNonEmptySetOrNone (Ljava/lang/Iterable;)Larrow/core/Option;
public static final synthetic fun toNonEmptySetOrNone (Ljava/util/Set;)Larrow/core/Option;
Expand Down
2 changes: 2 additions & 0 deletions arrow-libs/core/arrow-core/api/arrow-core.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,8 @@ final inline fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?> (arrow.core
final inline fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?> (arrow.core/Ior<#A, #B>).arrow.core/handleErrorWith(kotlin/Function2<#B, #B, #B>, kotlin/Function1<#A, arrow.core/Ior<#C, #B>>): arrow.core/Ior<#C, #B> // arrow.core/handleErrorWith|[email protected]<0:0,0:1>(kotlin.Function2<0:1,0:1,0:1>;kotlin.Function1<0:0,arrow.core.Ior<0:2,0:1>>){0§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?> (arrow.core/NonEmptyList<#B>).arrow.core/mapOrAccumulate(kotlin/Function2<#A, #A, #A>, kotlin/Function2<arrow.core.raise/RaiseAccumulate<#A>, #B, #C>): arrow.core/Either<#A, arrow.core/NonEmptyList<#C>> // arrow.core/mapOrAccumulate|[email protected]<0:1>(kotlin.Function2<0:0,0:0,0:0>;kotlin.Function2<arrow.core.raise.RaiseAccumulate<0:0>,0:1,0:2>){0§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?> (arrow.core/NonEmptyList<#B>).arrow.core/mapOrAccumulate(kotlin/Function2<arrow.core.raise/RaiseAccumulate<#A>, #B, #C>): arrow.core/Either<arrow.core/NonEmptyList<#A>, arrow.core/NonEmptyList<#C>> // arrow.core/mapOrAccumulate|[email protected]<0:1>(kotlin.Function2<arrow.core.raise.RaiseAccumulate<0:0>,0:1,0:2>){0§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?> (arrow.core/NonEmptySet<#B>).arrow.core/mapOrAccumulate(kotlin/Function2<#A, #A, #A>, kotlin/Function2<arrow.core.raise/RaiseAccumulate<#A>, #B, #C>): arrow.core/Either<#A, arrow.core/NonEmptySet<#C>> // arrow.core/mapOrAccumulate|[email protected]<0:1>(kotlin.Function2<0:0,0:0,0:0>;kotlin.Function2<arrow.core.raise.RaiseAccumulate<0:0>,0:1,0:2>){0§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?> (arrow.core/NonEmptySet<#B>).arrow.core/mapOrAccumulate(kotlin/Function2<arrow.core.raise/RaiseAccumulate<#A>, #B, #C>): arrow.core/Either<arrow.core/NonEmptyList<#A>, arrow.core/NonEmptySet<#C>> // arrow.core/mapOrAccumulate|[email protected]<0:1>(kotlin.Function2<arrow.core.raise.RaiseAccumulate<0:0>,0:1,0:2>){0§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?> (kotlin.collections/Iterable<#A>).arrow.core/align(kotlin.collections/Iterable<#B>, kotlin/Function1<arrow.core/Ior<#A, #B>, #C>): kotlin.collections/List<#C> // arrow.core/align|[email protected]<0:0>(kotlin.collections.Iterable<0:1>;kotlin.Function1<arrow.core.Ior<0:0,0:1>,0:2>){0§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?> (kotlin.collections/Iterable<#A>).arrow.core/leftPadZip(kotlin.collections/Iterable<#B>, kotlin/Function2<#A?, #B, #C>): kotlin.collections/List<#C> // arrow.core/leftPadZip|[email protected]<0:0>(kotlin.collections.Iterable<0:1>;kotlin.Function2<0:0?,0:1,0:2>){0§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?> (kotlin.collections/Iterable<#A>).arrow.core/padZip(kotlin.collections/Iterable<#B>, kotlin/Function1<#A, #C>, kotlin/Function1<#B, #C>, kotlin/Function2<#A, #B, #C>): kotlin.collections/List<#C> // arrow.core/padZip|[email protected]<0:0>(kotlin.collections.Iterable<0:1>;kotlin.Function1<0:0,0:2>;kotlin.Function1<0:1,0:2>;kotlin.Function2<0:0,0:1,0:2>){0§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>}[0]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
@file:OptIn(ExperimentalTypeInference::class)

package arrow.core

import arrow.core.raise.RaiseAccumulate
import kotlin.experimental.ExperimentalTypeInference
import kotlin.jvm.JvmInline

@JvmInline
Expand Down Expand Up @@ -50,6 +54,17 @@ public value class NonEmptySet<out A> private constructor(
NonEmptyList(elements.zip(other))
}

public inline fun <E, A, B> NonEmptySet<A>.mapOrAccumulate(
combine: (E, E) -> E,
@BuilderInference transform: RaiseAccumulate<E>.(A) -> B
): Either<E, NonEmptySet<B>> =
elements.mapOrAccumulate(combine, transform).map { requireNotNull(it.toNonEmptySetOrNull()) }

public inline fun <E, A, B> NonEmptySet<A>.mapOrAccumulate(
@BuilderInference transform: RaiseAccumulate<E>.(A) -> B
): Either<NonEmptyList<E>, NonEmptySet<B>> =
elements.mapOrAccumulate(transform).map { requireNotNull(it.toNonEmptySetOrNull()) }

public fun <A> nonEmptySetOf(first: A, vararg rest: A): NonEmptySet<A> =
NonEmptySet(first, rest.asIterable())

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package arrow.core

import arrow.core.test.nonEmptySet
import arrow.core.test.stackSafeIteration
import io.kotest.assertions.withClue
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.kotest.property.Arb
import io.kotest.property.arbitrary.int
import io.kotest.property.arbitrary.negativeInt
import io.kotest.property.arbitrary.next
import io.kotest.property.arbitrary.orNull
import io.kotest.property.checkAll
Expand Down Expand Up @@ -75,5 +77,41 @@ class NonEmptySetTest {
}
}
}
}

@Test
fun mapOrAccumulateIsStackSafe() = runTest {
val acc = mutableSetOf<Int>()
val res = (0..stackSafeIteration())
.toNonEmptySetOrNull()!!
.mapOrAccumulate(String::plus) {
acc.add(it)
it
}
res shouldBe Either.Right(acc)
}

@Test
fun mapOrAccumulateAccumulatesErrors() = runTest {
checkAll(Arb.nonEmptySet(Arb.int(), range = 0 .. 20)) { nes ->
val res = nes.mapOrAccumulate { i ->
if (i % 2 == 0) i else raise(i)
}

val expected = nes.filterNot { it % 2 == 0 }
.toNonEmptyListOrNull()?.left() ?: nes.filter { it % 2 == 0 }.toNonEmptySetOrNull()!!.right()

res shouldBe expected
}
}

@Test
fun mapOrAccumulateAccumulatesErrorsWithCombineFunction() = runTest {
checkAll(Arb.nonEmptySet(Arb.negativeInt(), range = 0 .. 20)) { nes ->
val res = nes.mapOrAccumulate(String::plus) { i ->
if (i > 0) i else raise("Negative")
}

res shouldBe nes.map { "Negative" }.joinToString("").left()
}
}
}

0 comments on commit d6073dd

Please sign in to comment.