diff --git a/arrow-libs/core/arrow-core/api/arrow-core.api b/arrow-libs/core/arrow-core/api/arrow-core.api index 36efcbde9b9..c9495593591 100644 --- a/arrow-libs/core/arrow-core/api/arrow-core.api +++ b/arrow-libs/core/arrow-core/api/arrow-core.api @@ -1576,7 +1576,6 @@ public final class arrow/core/SequenceKt { public static final fun crosswalk (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/sequences/Sequence; public static final fun crosswalkMap (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun crosswalkNull (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/sequences/Sequence; - public static final fun crosswalkNullList (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Ljava/util/List; public static final fun crosswalkT (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Ljava/util/List; public static final fun filterOption (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; public static final fun flatten (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; @@ -1599,7 +1598,6 @@ public final class arrow/core/SequenceKt { public static final fun salign (Lkotlin/sequences/Sequence;Larrow/typeclasses/Semigroup;Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; public static final fun salign (Lkotlin/sequences/Sequence;Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function2;)Lkotlin/sequences/Sequence; public static final fun separateEither (Lkotlin/sequences/Sequence;)Lkotlin/Pair; - public static final fun separateEitherToPair (Lkotlin/sequences/Sequence;)Lkotlin/Pair; public static final fun separateValidated (Lkotlin/sequences/Sequence;)Lkotlin/Pair; public static final fun sequence (Lkotlin/sequences/Sequence;)Larrow/core/Either; public static final fun sequence (Lkotlin/sequences/Sequence;)Larrow/core/Option; @@ -1618,15 +1616,11 @@ public final class arrow/core/SequenceKt { public static final fun traverseValidated (Lkotlin/sequences/Sequence;Larrow/typeclasses/Semigroup;Lkotlin/jvm/functions/Function1;)Larrow/core/Validated; public static final fun unalign (Lkotlin/sequences/Sequence;)Lkotlin/Pair; public static final fun unalign (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/Pair; - public static final fun unalignToPair (Lkotlin/sequences/Sequence;)Lkotlin/Pair; - public static final fun unalignToPair (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/Pair; public static final fun uniteEither (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; public static final fun uniteValidated (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; public static final fun unweave (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/sequences/Sequence; public static final fun unzip (Lkotlin/sequences/Sequence;)Lkotlin/Pair; public static final fun unzip (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/Pair; - public static final fun unzipToPair (Lkotlin/sequences/Sequence;)Lkotlin/Pair; - public static final fun unzipToPair (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/Pair; public static final fun void (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; public static final fun widen (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; public static final fun zip (Lkotlin/sequences/Sequence;Lkotlin/sequences/Sequence;Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function3;)Lkotlin/sequences/Sequence; diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Result.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Result.kt index c64f4dea181..521e04d78e0 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Result.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Result.kt @@ -29,13 +29,12 @@ public inline fun <A, B> Result<A>.flatMap(transform: (value: A) -> Result<B>): * Compose a recovering [transform] operation on the failure value [Throwable] whilst flattening [Result]. * @see recoverCatching if you want run a function that catches and maps recovers with `(Throwable) -> A`. */ -public inline fun <A> Result<A>.handleErrorWith(transform: (throwable: Throwable) -> Result<A>): Result<A> { - contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return when (val exception = exceptionOrNull()) { - null -> this - else -> transform(exception) - } -} +@Deprecated( + "Prefer Kotlin Std Result.recoverCatching instead of handleErrorWith", + ReplaceWith("recoverCatching { transform(it).getOrThrow() }") +) +public inline fun <A> Result<A>.handleErrorWith(transform: (throwable: Throwable) -> Result<A>): Result<A> = + recoverCatching { transform(it).getOrThrow() } /** * Compose both: @@ -44,6 +43,10 @@ public inline fun <A> Result<A>.handleErrorWith(transform: (throwable: Throwable * * Combining the powers of [flatMap] and [handleErrorWith]. */ +@Deprecated( + "Prefer Kotlin Std Result.fold instead of redeemWith", + ReplaceWith("fold(transform, handleErrorWith)") +) public inline fun <A, B> Result<A>.redeemWith( handleErrorWith: (throwable: Throwable) -> Result<B>, transform: (value: A) -> Result<B> diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Sequence.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Sequence.kt index f5c200e325e..1395500faf1 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Sequence.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Sequence.kt @@ -8,7 +8,9 @@ package arrow.core import arrow.core.Either.Left import arrow.core.Either.Right import arrow.core.raise.RaiseAccumulate +import arrow.core.raise.either import arrow.core.raise.fold +import arrow.core.raise.option import arrow.typeclasses.Monoid import arrow.typeclasses.MonoidDeprecation import arrow.typeclasses.Semigroup @@ -389,10 +391,6 @@ public fun <A, K, V> Sequence<A>.crosswalkMap(f: (A) -> Map<K, V>): Map<K, Seque } } -@Deprecated( - "This function is actually terminal. Use crosswalkNullList(f) instead.", - ReplaceWith("this.crosswalk{a -> f(a).toList()}") -) public fun <A, B> Sequence<A>.crosswalkNull(f: (A) -> B?): Sequence<B>? = fold<A, Sequence<B>?>(emptySequence()) { bs, a -> Ior.fromNullables(f(a), bs)?.fold( @@ -402,15 +400,6 @@ public fun <A, B> Sequence<A>.crosswalkNull(f: (A) -> B?): Sequence<B>? = ) } -public fun <A, B> Sequence<A>.crosswalkNullList(f: (A) -> B?): List<B>? = - fold<A, List<B>?>(emptyList()) { bs, a -> - Ior.fromNullables(f(a), bs)?.fold( - { listOf(it) }, - ::identity, - { l, r -> listOf(l) + r } - ) - } - public fun <A> Sequence<Sequence<A>>.flatten(): Sequence<A> = flatMap(::identity) @@ -695,10 +684,6 @@ public fun <A> Sequence<A>.salign( * @receiver Iterable of [Either] * @return a tuple containing Sequence with [Either.Left] and another Sequence with its [Either.Right] values. */ -@Deprecated( - "This function is actually terminal. Use separateEitherToPair instead.", - ReplaceWith("separateEitherToPair()") -) public fun <A, B> Sequence<Either<A, B>>.separateEither(): Pair<Sequence<A>, Sequence<B>> = fold(sequenceOf<A>() to sequenceOf<B>()) { (lefts, rights), either -> when (either) { @@ -707,15 +692,6 @@ public fun <A, B> Sequence<Either<A, B>>.separateEither(): Pair<Sequence<A>, Seq } } -public fun <A, B> Sequence<Either<A, B>>.separateEitherToPair(): Pair<List<A>, List<B>> = - fold(listOf<A>() to listOf<B>()) { (lefts, rights), either -> - when (either) { - is Left -> lefts + either.value to rights - is Right -> lefts to rights + either.value - } - } - - /** * Separate the inner [Validated] values into the [Validated.Invalid] and [Validated.Valid]. * @@ -734,8 +710,12 @@ public fun <A, B> Sequence<Validated<A, B>>.separateValidated(): Pair<Sequence<A } } +@Deprecated( + "The sequence extension function is being deprecated in favor of the DSL.\n$NicheAPI", + ReplaceWith("let<Sequence<Either<E, A>>, Either<E, List<A>>> { s -> either<E, List<A>> { s.map<Either<E, A>, A> { it.bind<A>() }.toList<A>() } }", "arrow.core.raise.either") +) public fun <E, A> Sequence<Either<E, A>>.sequence(): Either<E, List<A>> = - traverse(::identity) + let { s -> either { s.map { it.bind() }.toList() } } @Deprecated( "sequenceEither is being renamed to sequence to simplify the Arrow API", @@ -744,8 +724,12 @@ public fun <E, A> Sequence<Either<E, A>>.sequence(): Either<E, List<A>> = public fun <E, A> Sequence<Either<E, A>>.sequenceEither(): Either<E, Sequence<A>> = sequence().map { it.asSequence() } +@Deprecated( + "The sequence extension function is being deprecated in favor of the Option DSL.\n$NicheAPI", + ReplaceWith("let<Sequence<Option<A>>, Option<List<A>>> { s -> option<List<A>> { s.map<Option<A>, A> { it.bind<A>() }.toList<A>() } }", "arrow.core.raise.option") +) public fun <A> Sequence<Option<A>>.sequence(): Option<List<A>> = - traverse(::identity) + let { s -> option { s.map { it.bind() }.toList() } } @Deprecated( "sequenceOption is being renamed to sequence to simplify the Arrow API", @@ -803,21 +787,14 @@ public fun <A> Sequence<A>.split(): Pair<Sequence<A>, A>? = public fun <A> Sequence<A>.tail(): Sequence<A> = drop(1) +@Deprecated( + "Traverse for Sequence is being deprecated in favor of Either DSL.\n$NicheAPI", + ReplaceWith("let<Sequence<A>, Either<E, List<B>>> { s -> either<E, List<B>> { s.map<A, B> { f(it).bind<B>() }.toList<B>() } }", "arrow.core.raise.either") +) @OptIn(ExperimentalTypeInference::class) @OverloadResolutionByLambdaReturnType -public fun <E, A, 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 acc = mutableListOf<B>() - forEach { a -> - when (val res = f(a)) { - is Right -> acc.add(res.value) - is Left -> return@traverse res - } - } - return acc.toList().right() -} +public fun <E, A, B> Sequence<A>.traverse(f: (A) -> Either<E, B>): Either<E, List<B>> = + let { s -> either { s.map { f(it).bind() }.toList() } } @Deprecated( "traverseEither is being renamed to traverse to simplify the Arrow API", @@ -826,21 +803,14 @@ public fun <E, A, B> Sequence<A>.traverse(f: (A) -> Either<E, B>): Either<E, Lis public fun <E, A, B> Sequence<A>.traverseEither(f: (A) -> Either<E, B>): Either<E, Sequence<B>> = traverse(f).map { it.asSequence() } +@Deprecated( + "Traverse for Sequence is being deprecated in favor of Either DSL.\n$NicheAPI", + ReplaceWith("let<Sequence<A>, Option<List<B>>> { s -> option<List<B>> { s.map<A, B> { f(it).bind<B>() }.toList<B>() } }", "arrow.core.raise.option") +) @OptIn(ExperimentalTypeInference::class) @OverloadResolutionByLambdaReturnType -public 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 acc = mutableListOf<B>() - forEach { a -> - when (val res = f(a)) { - is Some -> acc.add(res.value) - is None -> return@traverse res - } - } - return Some(acc) -} +public fun <A, B> Sequence<A>.traverse(f: (A) -> Option<B>): Option<List<B>> = + let { s -> option { s.map { f(it).bind() }.toList() } } @Deprecated( "traverseOption is being renamed to traverse to simplify the Arrow API", @@ -898,38 +868,25 @@ public fun <E, A, B> Sequence<A>.traverseValidated( ): Validated<E, Sequence<B>> = traverse(semigroup, f).map { it.asSequence() } -@Deprecated( - "This function is actually terminal. Use unalignToPair instead.", - ReplaceWith("unalignToPair()") -) -public fun <A, B> Sequence<Ior<A, B>>.unalign(): Pair<Sequence<A>, Sequence<B>> = - fold(emptySequence<A>() to emptySequence()) { (l, r), x -> - x.fold( - { l + it to r }, - { l to r + it }, - { a, b -> l + a to r + b } - ) - } - /** * splits an union into its component parts. * * ```kotlin * import arrow.core.bothIor * import arrow.core.leftIor - * import arrow.core.unalignToPair + * import arrow.core.unalign * * fun main(args: Array<String>) { * //sampleStart - * val result = sequenceOf(("A" to 1).bothIor(), ("B" to 2).bothIor(), "C".leftIor()).unalignToPair() + * val result = sequenceOf(("A" to 1).bothIor(), ("B" to 2).bothIor(), "C".leftIor()).unalign() * //sampleEnd * println("(${result.first}, ${result.second})") * } * ``` * <!--- KNIT example-sequence-12.kt --> */ -public fun <A, B> Sequence<Ior<A, B>>.unalignToPair(): Pair<List<A>, List<B>> = - fold(emptyList<A>() to emptyList()) { (l, r), x -> +public fun <A, B> Sequence<Ior<A, B>>.unalign(): Pair<Sequence<A>, Sequence<B>> = + fold(emptySequence<A>() to emptySequence()) { (l, r), x -> x.fold( { l + it to r }, { l to r + it }, @@ -937,32 +894,24 @@ public fun <A, B> Sequence<Ior<A, B>>.unalignToPair(): Pair<List<A>, List<B>> = ) } - -@Deprecated( - "This function is actually terminal. Use unalignToPair instead.", - ReplaceWith("unalignToPair(fa)") -) -public fun <A, B, C> Sequence<C>.unalign(fa: (C) -> Ior<A, B>): Pair<Sequence<A>, Sequence<B>> = - map(fa).unalign() - /** * after applying the given function, splits the resulting union shaped structure into its components parts * * ```kotlin * import arrow.core.leftIor - * import arrow.core.unalignToPair + * import arrow.core.unalign * * fun main(args: Array<String>) { * //sampleStart - * val result = sequenceOf(1, 2, 3).unalignToPair { it.leftIor() } + * val result = sequenceOf(1, 2, 3).unalign { it.leftIor() } * //sampleEnd * println("(${result.first.toList()}, ${result.second.toList()})") * } * ``` * <!--- KNIT example-sequence-13.kt --> */ -public fun <A, B, C> Sequence<C>.unalignToPair(fa: (C) -> Ior<A, B>): Pair<List<A>, List<B>> = - map(fa).unalignToPair() +public fun <A, B, C> Sequence<C>.unalign(fa: (C) -> Ior<A, B>): Pair<Sequence<A>, Sequence<B>> = + map(fa).unalign() @Deprecated( NicheAPI + "Prefer using flatMap + fold", @@ -1007,53 +956,36 @@ public fun <A, B> Sequence<A>.unweave(ffa: (A) -> Sequence<B>): Sequence<B> = ffa(a).interleave(fa.unweave(ffa)) } ?: emptySequence() -@Deprecated( - "This function is actually terminal. Use unzipToPair instead.", - ReplaceWith("unzipToPair()") -) -public fun <A, B> Sequence<Pair<A, B>>.unzip(): Pair<Sequence<A>, Sequence<B>> = - fold(emptySequence<A>() to emptySequence()) { (l, r), x -> - l + x.first to r + x.second - } - /** * unzips the structure holding the resulting elements in an `Pair` * * ```kotlin - * import arrow.core.unzipToPair + * import arrow.core.unzip * * fun main(args: Array<String>) { * //sampleStart - * val result = sequenceOf("A" to 1, "B" to 2).unzipToPair() + * val result = sequenceOf("A" to 1, "B" to 2).unzip() * //sampleEnd * println("(${result.first}, ${result.second})") * } * ``` * <!--- KNIT example-sequence-15.kt --> */ -public fun <A, B> Sequence<Pair<A, B>>.unzipToPair(): Pair<List<A>, List<B>> = - fold(emptyList<A>() to emptyList()) { (l, r), x -> +public fun <A, B> Sequence<Pair<A, B>>.unzip(): Pair<Sequence<A>, Sequence<B>> = + fold(emptySequence<A>() to emptySequence()) { (l, r), x -> l + x.first to r + x.second } - -@Deprecated( - "This function is actually terminal. Use unzipToPair instead.", - ReplaceWith("unzipToPair(fc)") -) -public fun <A, B, C> Sequence<C>.unzip(fc: (C) -> Pair<A, B>): Pair<Sequence<A>, Sequence<B>> = - map(fc).unzip() - /** * after applying the given function unzip the resulting structure into its elements. * * ```kotlin - * import arrow.core.unzipToPair + * import arrow.core.unzip * * fun main(args: Array<String>) { * //sampleStart * val result = - * sequenceOf("A:1", "B:2", "C:3").unzipToPair { e -> + * sequenceOf("A:1", "B:2", "C:3").unzip { e -> * e.split(":").let { * it.first() to it.last() * } @@ -1064,9 +996,8 @@ public fun <A, B, C> Sequence<C>.unzip(fc: (C) -> Pair<A, B>): Pair<Sequence<A>, * ``` * <!--- KNIT example-sequence-16.kt --> */ -public fun <A, B, C> Sequence<C>.unzipToPair(fc: (C) -> Pair<A, B>): Pair<List<A>, List<B>> = - map(fc).unzipToPair() - +public fun <A, B, C> Sequence<C>.unzip(fc: (C) -> Pair<A, B>): Pair<Sequence<A>, Sequence<B>> = + map(fc).unzip() @Deprecated( "void is being deprecated in favor of simple Iterable.map.\n$NicheAPI", diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/SequenceKTest.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/SequenceKTest.kt index 5903550d201..e9d8c4a2d72 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/SequenceKTest.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/SequenceKTest.kt @@ -248,16 +248,6 @@ class SequenceKTest : StringSpec({ } } - "crosswalk the sequence to a nullable function" { - checkAll(Arb.list(Arb.int())){ list -> - fun nullEvens(i: Int): Int? = if(i % 2 == 0) i else null - - val obtained = list.asSequence().crosswalkNullList { nullEvens(it) } - val expected = list.map { nullEvens(it) } - obtained?.size shouldBe expected.filterNotNull().size - } - } - "can align sequences - 1" { checkAll(Arb.sequence(Arb.unit()), Arb.sequence(Arb.unit())) { a, b -> a.align(b).toList().size shouldBe max(a.toList().size, b.toList().size) @@ -322,23 +312,6 @@ class SequenceKTest : StringSpec({ } } - "unzipToPair should unzip values in a Pair in a Sequence of Pairs" { - checkAll(Arb.list(Arb.pair(Arb.int(), Arb.int()))){ pairList -> - val obtained = pairList.asSequence().unzipToPair() - val expected = pairList.unzip() - obtained shouldBe expected - } - } - - "unzipToPair should unzip values in a Pair in a Sequence" { - checkAll(Arb.list(Arb.int())){ list -> - val obtained = list.asSequence().unzipToPair { n -> Pair(n, n)} - val expected = list.unzip { n -> Pair(n, n) } - obtained shouldBe expected - } - } - - "filterOption should filter None" { checkAll(Arb.list(Arb.option(Arb.int()))) { ints -> ints.asSequence().filterOption().toList() shouldBe ints.filterOption() @@ -352,20 +325,20 @@ class SequenceKTest : StringSpec({ else it.right() } - val (lefts, rights) = sequence.separateEitherToPair() + val (lefts, rights) = sequence.separateEither() - lefts to rights shouldBe ints.partition { it % 2 == 0 } + lefts.toList() to rights.toList() shouldBe ints.partition { it % 2 == 0 } } } "separateValidated" { checkAll(Arb.sequence(Arb.int())) { ints -> val sequence = ints.map { - if (it % 2 == 0) it.left() - else it.right() + if (it % 2 == 0) it.invalid() + else it.valid() } - val (invalids, valids) = sequence.separateEitherToPair() + val (invalids, valids) = sequence.separateValidated() invalids.toList() to valids.toList() shouldBe ints.partition { it % 2 == 0 } } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-12.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-12.kt index 4d3194f9f85..d23efa81b24 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-12.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-12.kt @@ -3,11 +3,11 @@ package arrow.core.examples.exampleSequence12 import arrow.core.bothIor import arrow.core.leftIor -import arrow.core.unalignToPair +import arrow.core.unalign fun main(args: Array<String>) { //sampleStart - val result = sequenceOf(("A" to 1).bothIor(), ("B" to 2).bothIor(), "C".leftIor()).unalignToPair() + val result = sequenceOf(("A" to 1).bothIor(), ("B" to 2).bothIor(), "C".leftIor()).unalign() //sampleEnd println("(${result.first}, ${result.second})") } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-13.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-13.kt index 6c530ffda5e..5f18e813bc1 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-13.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-13.kt @@ -2,11 +2,11 @@ package arrow.core.examples.exampleSequence13 import arrow.core.leftIor -import arrow.core.unalignToPair +import arrow.core.unalign fun main(args: Array<String>) { //sampleStart - val result = sequenceOf(1, 2, 3).unalignToPair { it.leftIor() } + val result = sequenceOf(1, 2, 3).unalign { it.leftIor() } //sampleEnd println("(${result.first.toList()}, ${result.second.toList()})") } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-15.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-15.kt index e84c1ad1a05..f565abbb968 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-15.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-15.kt @@ -1,11 +1,11 @@ // This file was automatically generated from Sequence.kt by Knit tool. Do not edit. package arrow.core.examples.exampleSequence15 -import arrow.core.unzipToPair +import arrow.core.unzip fun main(args: Array<String>) { //sampleStart - val result = sequenceOf("A" to 1, "B" to 2).unzipToPair() + val result = sequenceOf("A" to 1, "B" to 2).unzip() //sampleEnd println("(${result.first}, ${result.second})") } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-16.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-16.kt index 6baf2ab5141..41dba1d23d2 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-16.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-sequence-16.kt @@ -1,12 +1,12 @@ // This file was automatically generated from Sequence.kt by Knit tool. Do not edit. package arrow.core.examples.exampleSequence16 -import arrow.core.unzipToPair +import arrow.core.unzip fun main(args: Array<String>) { //sampleStart val result = - sequenceOf("A:1", "B:2", "C:3").unzipToPair { e -> + sequenceOf("A:1", "B:2", "C:3").unzip { e -> e.split(":").let { it.first() to it.last() } diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/DeclarationUtils.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/DeclarationUtils.kt index a126ce33857..bf2e2946650 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/DeclarationUtils.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/DeclarationUtils.kt @@ -11,3 +11,6 @@ val KSClassDeclaration.isSealed val KSClassDeclaration.isData get() = modifiers.contains(Modifier.DATA) + +val KSClassDeclaration.isValue + get() = modifiers.contains(Modifier.VALUE) diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/OpticsProcessor.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/OpticsProcessor.kt index 8571b26df52..a19014bcafb 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/OpticsProcessor.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/OpticsProcessor.kt @@ -32,7 +32,7 @@ class OpticsProcessor(private val codegen: CodeGenerator, private val logger: KS private fun processClass(klass: KSClassDeclaration) { // check that it is sealed or data - if (!klass.isSealed && !klass.isData) { + if (!klass.isSealed && !klass.isData && !klass.isValue) { logger.error(klass.qualifiedNameOrSimpleName.otherClassTypeErrorMessage, klass) return } diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/domain.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/domain.kt index 69d4f052206..159358715d1 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/domain.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/domain.kt @@ -50,6 +50,8 @@ typealias SealedClassDsl = Target.SealedClassDsl typealias DataClassDsl = Target.DataClassDsl +typealias ValueClassDsl = Target.ValueClassDsl + sealed class Target { abstract val foci: List<Focus> @@ -59,6 +61,9 @@ sealed class Target { data class Optional(override val foci: List<Focus>) : Target() data class SealedClassDsl(override val foci: List<Focus>) : Target() data class DataClassDsl(override val foci: List<Focus>) : Target() + data class ValueClassDsl(val focus: Focus) : Target() { + override val foci = listOf(focus) + } } typealias NonNullFocus = Focus.NonNull diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/dsl.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/dsl.kt index 35aca64ed08..4ff9a1dd189 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/dsl.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/dsl.kt @@ -32,6 +32,16 @@ fun generatePrismDsl(ele: ADT, isoOptic: SealedClassDsl): Snippet { ) } +fun generateIsoDsl(ele: ADT, isoOptic: ValueClassDsl): Snippet { + val (className, import) = resolveClassName(ele) + return Snippet( + `package` = ele.packageName, + name = ele.simpleName, + content = processIsoSyntax(ele, isoOptic, className), + imports = setOf(import) + ) +} + private fun processLensSyntax(ele: ADT, foci: List<Focus>, className: String): String { return if (ele.typeParameters.isEmpty()) { foci.joinToString(separator = "\n") { focus -> @@ -137,6 +147,40 @@ private fun processPrismSyntax(ele: ADT, dsl: SealedClassDsl, className: String) } } +private fun processIsoSyntax(ele: ADT, dsl: ValueClassDsl, className: String): String = + if (ele.typeParameters.isEmpty()) { + dsl.foci.joinToString(separator = "\n\n") { focus -> + """ + |${ele.visibilityModifierName} inline val <S> $Iso<S, ${ele.sourceClassName}>.${focus.paramName}: $Iso<S, ${focus.className}> inline get() = this + ${className}.${focus.paramName} + |${ele.visibilityModifierName} inline val <S> $Lens<S, ${ele.sourceClassName}>.${focus.paramName}: $Lens<S, ${focus.className}> inline get() = this + ${className}.${focus.paramName} + |${ele.visibilityModifierName} inline val <S> $Optional<S, ${ele.sourceClassName}>.${focus.paramName}: $Optional<S, ${focus.className}> inline get() = this + ${className}.${focus.paramName} + |${ele.visibilityModifierName} inline val <S> $Prism<S, ${ele.sourceClassName}>.${focus.paramName}: $Prism<S, ${focus.className}> inline get() = this + ${className}.${focus.paramName} + |${ele.visibilityModifierName} inline val <S> $Setter<S, ${ele.sourceClassName}>.${focus.paramName}: $Setter<S, ${focus.className}> inline get() = this + ${className}.${focus.paramName} + |${ele.visibilityModifierName} inline val <S> $Traversal<S, ${ele.sourceClassName}>.${focus.paramName}: $Traversal<S, ${focus.className}> inline get() = this + ${className}.${focus.paramName} + |${ele.visibilityModifierName} inline val <S> $Fold<S, ${ele.sourceClassName}>.${focus.paramName}: $Fold<S, ${focus.className}> inline get() = this + ${className}.${focus.paramName} + |${ele.visibilityModifierName} inline val <S> $Every<S, ${ele.sourceClassName}>.${focus.paramName}: $Every<S, ${focus.className}> inline get() = this + ${className}.${focus.paramName} + |""".trimMargin() + } + } else { + dsl.foci.joinToString(separator = "\n\n") { focus -> + val sourceClassNameWithParams = focus.refinedType?.qualifiedString() ?: "${ele.sourceClassName}${ele.angledTypeParameters}" + val joinedTypeParams = when { + focus.refinedArguments.isEmpty() -> "" + else -> focus.refinedArguments.joinToString(separator=",") + } + """ + |${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Iso<S, $sourceClassNameWithParams>.${focus.paramName}(): $Iso<S, ${focus.className}> = this + ${className}.${focus.paramName}() + |${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Lens<S, $sourceClassNameWithParams>.${focus.paramName}(): $Lens<S, ${focus.className}> = this + ${className}.${focus.paramName}() + |${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Optional<S, $sourceClassNameWithParams>.${focus.paramName}(): $Optional<S, ${focus.className}> = this + ${className}.${focus.paramName}() + |${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Prism<S, $sourceClassNameWithParams>.${focus.paramName}(): $Prism<S, ${focus.className}> = this + ${className}.${focus.paramName}() + |${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Setter<S, $sourceClassNameWithParams>.${focus.paramName}(): $Setter<S, ${focus.className}> = this + ${className}.${focus.paramName}() + |${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Traversal<S, $sourceClassNameWithParams>.${focus.paramName}(): $Traversal<S, ${focus.className}> = this + ${className}.${focus.paramName}() + |${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Fold<S, $sourceClassNameWithParams>.${focus.paramName}(): $Fold<S, ${focus.className}> = this + ${className}.${focus.paramName}() + |${ele.visibilityModifierName} inline fun <S,$joinedTypeParams> $Every<S, $sourceClassNameWithParams>.${focus.paramName}(): $Every<S, ${focus.className}> = this + ${className}.${focus.paramName}() + |""".trimMargin() + } + } + private fun resolveClassName(ele: ADT): Pair<String, String> = if (hasPackageCollisions(ele)) { val classNameAlias = ele.sourceClassName.replace(".", "") val aliasImport = "import ${ele.sourceClassName} as $classNameAlias" diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/errors.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/errors.kt index 1c9b65fd979..7d634ac6e21 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/errors.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/errors.kt @@ -5,7 +5,7 @@ val String.otherClassTypeErrorMessage """ |$this cannot be annotated with @optics | ^ - |Only data and sealed classes can be annotated with @optics""".trimMargin() + |Only data, sealed, and value classes can be annotated with @optics""".trimMargin() val String.typeParametersErrorMessage get() = @@ -47,7 +47,7 @@ val String.isoErrorMessage |Cannot generate arrow.optics.Iso for $this | ^ |arrow.optics.OpticsTarget.ISO is an invalid @optics argument for $this. - |It is only valid for data classes. + |It is only valid for data and value classes. """.trimMargin() val String.isoTooBigErrorMessage diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/isos.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/isos.kt index dce33e5b3e0..64a89d6a746 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/isos.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/isos.kt @@ -1,5 +1,7 @@ package arrow.optics.plugin.internals +import arrow.optics.plugin.isValue + internal fun generateIsos(ele: ADT, target: IsoTarget) = Snippet(`package` = ele.packageName, name = ele.simpleName, content = processElement(ele, target)) @@ -63,12 +65,13 @@ private fun processElement(iso: ADT, target: Target): String { "tuple: ${focusType()} -> ${(foci.indices).joinToString(prefix = "${iso.sourceClassName}(", postfix = ")", transform = { "tuple.${letters[it]}" })}" } + val isoName = if (iso.declaration.isValue) target.foci.first().paramName else "iso" val sourceClassNameWithParams = "${iso.sourceClassName}${iso.angledTypeParameters}" val firstLine = when { iso.typeParameters.isEmpty() -> - "${iso.visibilityModifierName} inline val ${iso.sourceClassName}.Companion.iso: $Iso<${iso.sourceClassName}, ${focusType()}> inline get()" + "${iso.visibilityModifierName} inline val ${iso.sourceClassName}.Companion.$isoName: $Iso<${iso.sourceClassName}, ${focusType()}> inline get()" else -> - "${iso.visibilityModifierName} inline fun ${iso.angledTypeParameters} ${iso.sourceClassName}.Companion.iso(): $Iso<$sourceClassNameWithParams, ${focusType()}>" + "${iso.visibilityModifierName} inline fun ${iso.angledTypeParameters} ${iso.sourceClassName}.Companion.$isoName(): $Iso<$sourceClassNameWithParams, ${focusType()}>" } return """ diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/processor.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/processor.kt index 4ef66b4dce9..9ef7f4f2809 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/processor.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/processor.kt @@ -2,6 +2,7 @@ package arrow.optics.plugin.internals import arrow.optics.plugin.isData import arrow.optics.plugin.isSealed +import arrow.optics.plugin.isValue import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.symbol.* import java.util.Locale @@ -18,8 +19,12 @@ internal fun adt(c: KSClassDeclaration, logger: KSPLogger): ADT = OpticsTarget.OPTIONAL -> evalAnnotatedDataClass(c, c.qualifiedNameOrSimpleName.optionalErrorMessage, logger) .let(::OptionalTarget) - OpticsTarget.ISO -> evalAnnotatedIsoElement(c, logger).let(::IsoTarget) - OpticsTarget.PRISM -> evalAnnotatedPrismElement(c, logger).let(::PrismTarget) + OpticsTarget.ISO -> + evalAnnotatedIsoElement(c, c.qualifiedNameOrSimpleName.isoErrorMessage, logger) + .let(::IsoTarget) + OpticsTarget.PRISM -> + evalAnnotatedPrismElement(c, c.qualifiedNameOrSimpleName.prismErrorMessage, logger) + .let(::PrismTarget) OpticsTarget.DSL -> evalAnnotatedDslElement(c, logger) } } @@ -32,6 +37,9 @@ internal fun KSClassDeclaration.targets(): List<OpticsTarget> = if (targets.isEmpty()) listOf(OpticsTarget.PRISM, OpticsTarget.DSL) else targets.filter { it == OpticsTarget.PRISM || it == OpticsTarget.DSL } + isValue -> + listOf(OpticsTarget.ISO, OpticsTarget.DSL) + .filter { targets.isEmpty() || it in targets } else -> if (targets.isEmpty()) listOf(OpticsTarget.ISO, OpticsTarget.LENS, OpticsTarget.OPTIONAL, OpticsTarget.DSL) @@ -62,6 +70,7 @@ internal fun KSClassDeclaration.targetsFromOpticsAnnotation(): List<OpticsTarget internal fun evalAnnotatedPrismElement( element: KSClassDeclaration, + errorMessage: String, logger: KSPLogger ): List<Focus> = when { @@ -74,7 +83,7 @@ internal fun evalAnnotatedPrismElement( ) }.toList() else -> { - logger.error(element.qualifiedNameOrSimpleName.prismErrorMessage, element) + logger.error(errorMessage, element) emptyList() } } @@ -109,11 +118,20 @@ internal fun evalAnnotatedDslElement(element: KSClassDeclaration, logger: KSPLog .getConstructorTypesNames() .zip(element.getConstructorParamNames(), Focus.Companion::invoke) ) - element.isSealed -> SealedClassDsl(evalAnnotatedPrismElement(element, logger)) - else -> throw IllegalStateException("should only be sealed or data by now") + element.isValue -> + ValueClassDsl( + Focus(element.getConstructorTypesNames().first(), element.getConstructorParamNames().first()) + ) + element.isSealed -> + SealedClassDsl(evalAnnotatedPrismElement(element, element.qualifiedNameOrSimpleName.prismErrorMessage, logger)) + else -> throw IllegalStateException("should only be sealed, data, or value by now") } -internal fun evalAnnotatedIsoElement(element: KSClassDeclaration, logger: KSPLogger): List<Focus> = +internal fun evalAnnotatedIsoElement( + element: KSClassDeclaration, + errorMessage: String, + logger: KSPLogger +): List<Focus> = when { element.isData -> element @@ -124,8 +142,10 @@ internal fun evalAnnotatedIsoElement(element: KSClassDeclaration, logger: KSPLog logger.error(element.qualifiedNameOrSimpleName.isoTooBigErrorMessage, element) emptyList() } + element.isValue -> + listOf(Focus(element.getConstructorTypesNames().first(), element.getConstructorParamNames().first())) else -> { - logger.error(element.qualifiedNameOrSimpleName.isoErrorMessage, element) + logger.error(errorMessage, element) emptyList() } } diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/snippets.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/snippets.kt index 6252cb46990..ebb6924bf75 100644 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/snippets.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/main/kotlin/arrow/optics/plugin/internals/snippets.kt @@ -9,6 +9,7 @@ internal fun ADT.snippets(): List<Snippet> = is OptionalTarget -> generateOptionals(this, it) is SealedClassDsl -> generatePrismDsl(this, it) is DataClassDsl -> generateOptionalDsl(this, it) + generateLensDsl(this, it) + is ValueClassDsl -> generateIsoDsl(this, it) } } diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/IsoTests.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/IsoTests.kt index 5ed4b3d6556..3c4885a69b6 100755 --- a/arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/IsoTests.kt +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/IsoTests.kt @@ -99,4 +99,31 @@ class IsoTests { |} """.failsWith { it.contains("${`package`.removePrefix("package ")}.IsoXXL".isoTooBigErrorMessage) } } + + @Test + fun `Isos will be generated for value class`() { + """ + |$`package` + |$imports + |@optics @JvmInline + |value class IsoData( + | val field1: String + |) { companion object } + | + |val i: Iso<IsoData, String> = IsoData.field1 + |val r = i != null + """.evals("r" to true) + } + + @Test + fun `Iso generation requires companion object declaration, value class`() { + """ + |$`package` + |$imports + |@optics @JvmInline + |value class IsoNoCompanion( + | val field1: String + |) + """.failsWith { it.contains("IsoNoCompanion".noCompanion) } + } } diff --git a/arrow-libs/stack/build.gradle.kts b/arrow-libs/stack/build.gradle.kts index 4435af4134a..200af1a15fb 100644 --- a/arrow-libs/stack/build.gradle.kts +++ b/arrow-libs/stack/build.gradle.kts @@ -11,14 +11,14 @@ dependencies { constraints { api("io.arrow-kt:arrow-annotations:$version") api("io.arrow-kt:arrow-continuations:$version") + api("io.arrow-kt:arrow-atomic:$version") api("io.arrow-kt:arrow-core:$version") api("io.arrow-kt:arrow-core-retrofit:$version") - api("io.arrow-kt:arrow-core-test:$version") api("io.arrow-kt:arrow-fx-coroutines:$version") - api("io.arrow-kt:arrow-fx-coroutines-test:$version") api("io.arrow-kt:arrow-fx-stm:$version") - api("io.arrow-kt:arrow-meta:$version") + api("io.arrow-kt:arrow-resilience:$version") api("io.arrow-kt:arrow-optics:$version") - api("io.arrow-kt:arrow-optics-test:$version") + api("io.arrow-kt:arrow-optics-reflect:$version") + api("io.arrow-kt:arrow-optics-ksp-plugin:$version") } }