From 55ecde7fb8fcdc46241e97bf7318399cba8fd2d9 Mon Sep 17 00:00:00 2001 From: Youssef Shoaib Date: Fri, 24 Feb 2023 08:00:02 +0000 Subject: [PATCH 1/6] Update Option to be a value class --- .../commonMain/kotlin/arrow/core/Either.kt | 59 +- .../commonMain/kotlin/arrow/core/Iterable.kt | 22 +- .../commonMain/kotlin/arrow/core/Option.kt | 1206 +++++++++-------- .../commonMain/kotlin/arrow/core/Sequence.kt | 2 +- .../src/commonMain/kotlin/arrow/core/map.kt | 4 +- .../kotlin/arrow/core/raise/Builders.kt | 47 +- .../kotlin/arrow/core/raise/Effect.kt | 2 +- .../kotlin/arrow/core/raise/Mappers.kt | 8 +- .../kotlin/arrow/core/raise/Raise.kt | 81 +- .../kotlin/arrow/typeclasses/Monoid.kt | 4 +- .../kotlin/arrow/typeclasses/Semigroup.kt | 4 +- .../kotlin/arrow/core/EitherTest.kt | 2 +- .../kotlin/arrow/core/OptionTest.kt | 42 +- .../kotlin/arrow/core/raise/NullableSpec.kt | 2 + .../kotlin/arrow/core/raise/OptionSpec.kt | 5 +- .../kotlin/arrow/core/test/Generators.kt | 2 +- .../jvmTest/java/arrow/core/DeadlockTest.kt | 2 +- .../jvmTest/java/arrow/core/OptionUsage.java | 17 - .../kotlin/examples/example-either-41.kt | 12 +- .../kotlin/examples/example-either-42.kt | 7 +- .../kotlin/examples/example-either-43.kt | 8 - .../kotlin/examples/example-either-44.kt | 10 + .../kotlin/examples/example-option-01.kt | 4 +- .../kotlin/examples/example-option-02.kt | 4 +- .../kotlin/examples/example-option-03.kt | 5 +- .../kotlin/examples/example-option-04.kt | 5 +- .../kotlin/examples/example-option-05.kt | 8 +- .../kotlin/examples/example-option-06.kt | 2 +- .../kotlin/examples/example-option-07.kt | 12 +- .../kotlin/examples/example-option-08.kt | 12 +- .../kotlin/examples/example-option-09.kt | 4 +- .../kotlin/examples/example-option-10.kt | 3 +- .../kotlin/examples/example-option-11.kt | 1 + .../kotlin/examples/example-option-12.kt | 3 +- .../kotlin/examples/example-option-13.kt | 2 +- .../kotlin/examples/example-option-14.kt | 4 +- .../kotlin/examples/example-option-15.kt | 2 +- .../kotlin/examples/example-option-16.kt | 2 +- .../kotlin/examples/example-option-17.kt | 1 + .../kotlin/examples/example-option-18.kt | 1 + .../kotlin/examples/example-option-19.kt | 1 + .../kotlin/examples/example-option-20.kt | 1 + .../kotlin/examples/example-raise-03.kt | 2 +- .../kotlin/examples/example-raise-dsl-06.kt | 7 +- .../kotlin/examples/test/EitherKnitTest.kt | 4 +- .../kotlin/arrow/optics/Optional.kt | 9 +- .../commonMain/kotlin/arrow/optics/Prism.kt | 10 +- .../kotlin/arrow/optics/typeclasses/At.kt | 3 +- .../kotlin/arrow/optics/OptionalTest.kt | 3 + 49 files changed, 857 insertions(+), 806 deletions(-) delete mode 100644 arrow-libs/core/arrow-core/src/jvmTest/java/arrow/core/OptionUsage.java diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Either.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Either.kt index 3624f9f975b..df46e49ffcf 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Either.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Either.kt @@ -1064,30 +1064,7 @@ public sealed class Either { } return getOrElse { null } } - - public fun orNone(): Option = getOrNone() - - /** - * Transforms [Either] into [Option], - * where the encapsulated value [B] is wrapped in [Some] when this instance represents [Either.Right], - * or [None] if it is [Either.Left]. - * - * ```kotlin - * import arrow.core.Either - * import arrow.core.Some - * import arrow.core.None - * import io.kotest.matchers.shouldBe - * - * fun test() { - * Either.Right(12).getOrNone() shouldBe Some(12) - * Either.Left(12).getOrNone() shouldBe None - * } - * ``` - * - * - */ - public fun getOrNone(): Option = fold({ None }, { Some(it) }) - + @Deprecated( NicheAPI + "Prefer using the Either DSL, or map", ReplaceWith("if (n <= 0) Right(emptyList()) else map { b -> List(n) { b } }") @@ -1119,7 +1096,7 @@ public sealed class Either { * Either.Right("foo").isEmpty() // Result: false * } * ``` - * + * */ @Deprecated( RedundantAPI + "Use `is Either.Left<*>`, `when`, or `fold` instead", @@ -1141,7 +1118,7 @@ public sealed class Either { * //sampleEnd * } * ``` - * + * */ @Deprecated( RedundantAPI + "Use `is Either.Right<*>`, `when`, or `fold` instead", @@ -1319,7 +1296,7 @@ public sealed class Either { * println(result) * } * ``` - * + * */ @JvmStatic @Deprecated( @@ -1816,6 +1793,30 @@ public sealed class Either { map { } } +public inline fun Either.orNone(): Option = getOrNone() + +/** + * Transforms [Either] into [Option], + * where the encapsulated value [B] is wrapped in [Some] when this instance represents [Either.Right], + * or [None] if it is [Either.Left]. + * + * ```kotlin + * import arrow.core.Either + * import arrow.core.Some + * import arrow.core.None + * import arrow.core.getOrNone + * import io.kotest.matchers.shouldBe + * + * fun test() { + * Either.Right(12).getOrNone() shouldBe Some(12) + * Either.Left(12).getOrNone() shouldBe None + * } + * ``` + * + * + */ +public inline fun Either.getOrNone(): Option = fold({ None }, { Some(it) }) + /** * Binds the given function across [Right], that is, * Map, or transform, the right value [B] of this [Either] into a new [Either] with a right value of type [C]. @@ -2238,7 +2239,7 @@ public fun Either>.sequence(): List> = "arrow.core.right", ) ) -public fun Either>.sequenceOption(): Option> = +public inline fun Either>.sequenceOption(): Option> = sequence() @Deprecated( @@ -2250,7 +2251,7 @@ public fun Either>.sequenceOption(): Option> = "arrow.core.left" ) ) -public fun Either>.sequence(): Option> = +public inline fun Either>.sequence(): Option> = orNull()?.orNull()?.right().toOption() @Deprecated( diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Iterable.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Iterable.kt index 5ad62be1217..ce0045cb808 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Iterable.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Iterable.kt @@ -720,7 +720,7 @@ public fun Iterable.combineAll(MA: Monoid): A = /** * Returns the first element as [Some(element)][Some], or [None] if the iterable is empty. */ -public fun Iterable.firstOrNone(): Option = +public inline fun Iterable.firstOrNone(): Option = when (this) { is Collection -> if (!isEmpty()) { Some(first()) @@ -733,7 +733,7 @@ public fun Iterable.firstOrNone(): Option = } } -private fun Iterator.nextOrNone(): Option = +@PublishedApi internal inline fun Iterator.nextOrNone(): Option = if (hasNext()) { Some(next()) } else { @@ -743,7 +743,7 @@ private fun Iterator.nextOrNone(): Option = /** * Returns the first element as [Some(element)][Some] matching the given [predicate], or [None] if element was not found. */ -public inline fun Iterable.firstOrNone(predicate: (T) -> Boolean): Option { +public inline fun Iterable.firstOrNone(predicate: (T) -> Boolean): Option { for (element in this) { if (predicate(element)) { return Some(element) @@ -755,7 +755,7 @@ public inline fun Iterable.firstOrNone(predicate: (T) -> Boolean): Option /** * Returns single element as [Some(element)][Some], or [None] if the iterable is empty or has more than one element. */ -public fun Iterable.singleOrNone(): Option = +public inline fun Iterable.singleOrNone(): Option = when (this) { is Collection -> when (size) { 1 -> firstOrNone() @@ -770,7 +770,7 @@ public fun Iterable.singleOrNone(): Option = /** * Returns the single element as [Some(element)][Some] matching the given [predicate], or [None] if element was not found or more than one element was found. */ -public inline fun Iterable.singleOrNone(predicate: (T) -> Boolean): Option { +public inline fun Iterable.singleOrNone(predicate: (T) -> Boolean): Option { val list = mutableListOf() for (element in this) { if (predicate(element)) { @@ -786,7 +786,7 @@ public inline fun Iterable.singleOrNone(predicate: (T) -> Boolean): Optio /** * Returns the last element as [Some(element)][Some], or [None] if the iterable is empty. */ -public fun Iterable.lastOrNone(): Option = +public inline fun Iterable.lastOrNone(): Option = when (this) { is Collection -> if (!isEmpty()) { Some(last()) @@ -808,7 +808,7 @@ public fun Iterable.lastOrNone(): Option = /** * Returns the last element as [Some(element)][Some] matching the given [predicate], or [None] if no such element was found. */ -public inline fun Iterable.lastOrNone(predicate: (T) -> Boolean): Option { +public inline fun Iterable.lastOrNone(predicate: (T) -> Boolean): Option { var value: Any? = EmptyValue for (element in this) { if (predicate(element)) { @@ -821,7 +821,7 @@ public inline fun Iterable.lastOrNone(predicate: (T) -> Boolean): Option< /** * Returns an element as [Some(element)][Some] at the given [index] or [None] if the [index] is out of bounds of this iterable. */ -public fun Iterable.elementAtOrNone(index: Int): Option = +public inline fun Iterable.elementAtOrNone(index: Int): Option = when { index < 0 -> None this is Collection -> when (index) { @@ -832,7 +832,7 @@ public fun Iterable.elementAtOrNone(index: Int): Option = else -> iterator().skip(index).nextOrNone() } -private tailrec fun Iterator.skip(count: Int): Iterator = +@PublishedApi internal tailrec fun Iterator.skip(count: Int): Iterator = when { count > 0 && hasNext() -> { next() @@ -1048,7 +1048,7 @@ public operator fun > Iterable.compareTo(other: Iterable public infix fun T.prependTo(list: Iterable): List = listOf(this) + list -public fun Iterable>.filterOption(): List = +public inline fun Iterable>.filterOption(): List = flatMap { it.fold(::emptyList, ::listOf) } -public fun Iterable>.flattenOption(): List = filterOption() +public inline fun Iterable>.flattenOption(): List = filterOption() diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Option.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Option.kt index 3b69109b619..bdb83b03b96 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Option.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Option.kt @@ -1,7 +1,8 @@ @file:OptIn(ExperimentalContracts::class) + package arrow.core -import arrow.core.Either.Right +import arrow.core.Option.Companion.fromNullable import arrow.core.raise.OptionRaise import arrow.core.raise.option import arrow.typeclasses.Monoid @@ -9,9 +10,16 @@ import arrow.typeclasses.Semigroup import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract +import kotlin.jvm.JvmInline import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic +@RequiresOptIn( + message = + "This declaration is part of the internals of Option. Great caution must be taken to avoid ClassCastExceptions" +) +internal annotation class OptionInternals + /** * * @@ -23,9 +31,7 @@ import kotlin.jvm.JvmStatic * `Option` is a container for an optional value of type `A`. If the value of type `A` is present, the `Option` is an instance of `Some`, containing the present value of type `A`. If the value is absent, the `Option` is the object `None`. * * ```kotlin - * import arrow.core.Option - * import arrow.core.Some - * import arrow.core.none + * import arrow.core.* * * //sampleStart * val someValue: Option = Some("I am wrapped in something") @@ -41,9 +47,7 @@ import kotlin.jvm.JvmStatic * Let's write a function that may or may not give us a string, thus returning `Option`: * * ```kotlin - * import arrow.core.None - * import arrow.core.Option - * import arrow.core.Some + * import arrow.core.* * * //sampleStart * fun maybeItWillReturnSomething(flag: Boolean): Option = @@ -55,10 +59,7 @@ import kotlin.jvm.JvmStatic * Using `getOrElse`, we can provide a default value `"No value"` when the optional argument `None` does not exist: * * ```kotlin - * import arrow.core.None - * import arrow.core.Option - * import arrow.core.Some - * import arrow.core.getOrElse + * import arrow.core.* * * fun maybeItWillReturnSomething(flag: Boolean): Option = * if (flag) Some("Found value") else None @@ -75,10 +76,7 @@ import kotlin.jvm.JvmStatic * * * ```kotlin - * import arrow.core.None - * import arrow.core.Option - * import arrow.core.Some - * import arrow.core.getOrElse + * import arrow.core.* * * fun maybeItWillReturnSomething(flag: Boolean): Option = * if (flag) Some("Found value") else None @@ -97,16 +95,14 @@ import kotlin.jvm.JvmStatic * Checking whether option has value: * * ```kotlin - * import arrow.core.None - * import arrow.core.Option - * import arrow.core.Some + * import arrow.core.* * * fun maybeItWillReturnSomething(flag: Boolean): Option = * if (flag) Some("Found value") else None * * //sampleStart - * val valueSome = maybeItWillReturnSomething(true) is None - * val valueNone = maybeItWillReturnSomething(false) is None + * val valueSome = maybeItWillReturnSomething(true).isEmpty() + * val valueNone = maybeItWillReturnSomething(false).isEmpty() * //sampleEnd * fun main() { * println("valueSome = $valueSome") @@ -117,7 +113,7 @@ import kotlin.jvm.JvmStatic * Creating a `Option` of a `T?`. Useful for working with values that can be nullable: * * ```kotlin - * import arrow.core.Option + * import arrow.core.* * * //sampleStart * val myString: String? = "Nullable string" @@ -132,16 +128,14 @@ import kotlin.jvm.JvmStatic * Option can also be used with when statements: * * ```kotlin - * import arrow.core.None - * import arrow.core.Option - * import arrow.core.Some + * import arrow.core.* * * //sampleStart * val someValue: Option = Some(20.0) - * val value = when(someValue) { - * is Some -> someValue.value - * is None -> 0.0 - * } + * val value = someValue.fold( + * { 0.0 }, + * { it } + * ) * //sampleEnd * fun main () { * println("value = $value") @@ -150,16 +144,14 @@ import kotlin.jvm.JvmStatic * * * ```kotlin - * import arrow.core.None - * import arrow.core.Option - * import arrow.core.Some + * import arrow.core.* * * //sampleStart * val noValue: Option = None - * val value = when(noValue) { - * is Some -> noValue.value - * is None -> 0.0 - * } + * val value = noValue.fold( + * { 0.0 }, + * { it } + * ) * //sampleEnd * fun main () { * println("value = $value") @@ -172,9 +164,7 @@ import kotlin.jvm.JvmStatic * One of these operations is `map`. This operation allows us to map the inner value to a different type while preserving the option: * * ```kotlin - * import arrow.core.None - * import arrow.core.Option - * import arrow.core.Some + * import arrow.core.* * * //sampleStart * val number: Option = Some(3) @@ -193,8 +183,7 @@ import kotlin.jvm.JvmStatic * Another operation is `fold`. This operation will extract the value from the option, or provide a default if the value is `None` * * ```kotlin - * import arrow.core.Option - * import arrow.core.Some + * import arrow.core.* * * val fold = * //sampleStart @@ -209,6 +198,7 @@ import kotlin.jvm.JvmStatic * ```kotlin * import arrow.core.Option * import arrow.core.none + * import arrow.core.fold * * val fold = * //sampleStart @@ -223,8 +213,7 @@ import kotlin.jvm.JvmStatic * Arrow also adds syntax to all datatypes so you can easily lift them into the context of `Option` where needed. * * ```kotlin - * import arrow.core.some - * import arrow.core.none + * import arrow.core.* * * //sampleStart * val some = 1.some() @@ -238,7 +227,7 @@ import kotlin.jvm.JvmStatic * * * ```kotlin - * import arrow.core.toOption + * import arrow.core.* * * //sampleStart * val nullString: String? = null @@ -257,9 +246,7 @@ import kotlin.jvm.JvmStatic * You can easily convert between `A?` and `Option` by using the `toOption()` extension or `Option.fromNullable` constructor. * * ```kotlin - * import arrow.core.firstOrNone - * import arrow.core.toOption - * import arrow.core.Option + * import arrow.core.* * * //sampleStart * val foxMap = mapOf(1 to "The", 2 to "Quick", 3 to "Brown", 4 to "Fox") @@ -278,7 +265,7 @@ import kotlin.jvm.JvmStatic * ### Transforming the inner contents * * ```kotlin - * import arrow.core.Some + * import arrow.core.* * * fun main() { * val value = @@ -293,7 +280,7 @@ import kotlin.jvm.JvmStatic * ### Computing over independent values * * ```kotlin - * import arrow.core.Some + * import arrow.core.* * * val value = * //sampleStart @@ -310,22 +297,28 @@ import kotlin.jvm.JvmStatic * Contents partially adapted from [Scala Exercises Option Tutorial](https://www.scala-exercises.org/std_lib/options) * Originally based on the Scala Koans. */ -public sealed class Option { +@JvmInline +public value class Option @OptionInternals private constructor(@property:OptionInternals @PublishedApi internal val underlying: Any) { public companion object { + // This is simply an alias to constructor-impl + @PublishedApi + @OptionInternals + internal fun construct(value: Any): Option = Option(value) + @JvmStatic - public fun fromNullable(a: A?): Option = if (a != null) Some(a) else None + public inline fun fromNullable(a: A?): Option = if (a != null) Some(a) else None @JvmStatic - public operator fun invoke(a: A): Option = Some(a) + public inline operator fun invoke(a: A): Option = Some(a) @JvmStatic @JvmName("tryCatchOrNone") /** * Ignores exceptions and returns None if one is thrown */ - public inline fun catch(f: () -> A): Option { + public inline fun catch(f: () -> A): Option { contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } val recover: (Throwable) -> Option = { None } return catch(recover, f) @@ -333,7 +326,7 @@ public sealed class Option { @JvmStatic @JvmName("tryCatch") - public inline fun catch(recover: (Throwable) -> Option, f: () -> A): Option { + public inline fun catch(recover: (Throwable) -> Option, f: () -> A): Option { contract { callsInPlace(f, InvocationKind.EXACTLY_ONCE) callsInPlace(recover, InvocationKind.AT_MOST_ONCE) @@ -346,529 +339,650 @@ public sealed class Option { } @JvmStatic - public fun lift(f: (A) -> B): (Option) -> Option = + public inline fun lift(crossinline f: (A) -> B): (Option) -> Option = { it.map(f) } } - public fun zip(other: Option): Option> = - zip(other, ::Pair) + override fun toString(): String = fold( + { "Option.None" }, + { "Option.Some($it)" } + ) +} - public inline fun zip( - b: Option, - map: (A, B) -> C - ): Option { - contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } - return zip( - b, - Some.unit, - Some.unit, - Some.unit, - Some.unit, - Some.unit, - Some.unit, - Some.unit, - Some.unit - ) { b, c, _, _, _, _, _, _, _, _ -> map(b, c) } - } +@PublishedApi +internal inline fun isTypeOption(): Boolean = + boxedSomeUnit is T && Unit !is T // Checks that a known Option value inhertis from that type but that a regular Any value doesn't + +@PublishedApi +@OptionInternals +internal val Option.value: A + get() = + when (underlying) { + is NestedNull -> if (underlying.nesting == 0U) null else NestedNull(underlying.nesting - 1U) + is NestedNone -> + if (underlying.nesting == 0U) error("Cannot unpack the value of an Option.None") + else NestedNone(underlying.nesting - 1U) + + else -> underlying + } as A + +public inline fun Option.zip(other: Option): Option> = + zip(other, ::Pair) + +public inline fun Option.zip( + b: Option, + map: (A, B) -> C +): Option { + contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } + return zip( + b, + someUnit, + someUnit, + someUnit, + someUnit, + someUnit, + someUnit, + someUnit, + someUnit + ) { b, c, _, _, _, _, _, _, _, _ -> map(b, c) } +} - public inline fun zip( - b: Option, - c: Option, - map: (A, B, C) -> D - ): Option { - contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } - return zip( - b, - c, - Some.unit, - Some.unit, - Some.unit, - Some.unit, - Some.unit, - Some.unit, - Some.unit - ) { b, c, d, _, _, _, _, _, _, _ -> map(b, c, d) } - } +public inline fun Option.zip( + b: Option, + c: Option, + map: (A, B, C) -> D +): Option { + contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } + return zip( + b, + c, + someUnit, + someUnit, + someUnit, + someUnit, + someUnit, + someUnit, + someUnit + ) { b, c, d, _, _, _, _, _, _, _ -> map(b, c, d) } +} - /** - * The given function is applied as a fire and forget effect - * if this is a `None`. - * When applied the result is ignored and the original - * None value is returned - * - * Example: - * ```kotlin - * import arrow.core.Some - * import arrow.core.none - * - * fun main() { - * Some(12).tapNone { println("flower") } // Result: Some(12) - * none().tapNone { println("flower") } // Result: prints "flower" and returns: None - * } - * ``` - * - */ - public inline fun tapNone(f: () -> Unit): Option { - contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is None -> { - f() - this - } +/** + * The given function is applied as a fire and forget effect + * if this is a `None`. + * When applied the result is ignored and the original + * None value is returned + * + * Example: + * ```kotlin + * import arrow.core.Some + * import arrow.core.none + * import arrow.core.tapNone + * + * fun main() { + * Some(12).tapNone { println("flower") } // Result: Some(12) + * none().tapNone { println("flower") } // Result: prints "flower" and returns: None + * } + * ``` + * + */ +public inline fun Option.tapNone(f: () -> Unit): Option { + contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } + if (this == None) f() + return this +} - is Some -> this +/** + * The given function is applied as a fire and forget effect + * if this is a `some`. + * When applied the result is ignored and the original + * Some value is returned + * + * Example: + * ```kotlin + * import arrow.core.Some + * import arrow.core.none + * import arrow.core.tap + * + * fun main() { + * Some(12).tap { println("flower") } // Result: prints "flower" and returns: Some(12) + * none().tap { println("flower") } // Result: None + * } + * ``` + * + */ +public inline fun Option.tap(f: (A) -> Unit): Option { + contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } + return fold( + { + this + }, + { value -> + f(value) + this } + ) +} + +public inline fun Option.zip( + b: Option, + c: Option, + d: Option, + map: (A, B, C, D) -> E +): Option { + contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } + return zip( + b, + c, + d, + someUnit, + someUnit, + someUnit, + someUnit, + someUnit, + someUnit + ) { a, b, c, d, _, _, _, _, _, _ -> map(a, b, c, d) } +} + +public inline fun Option.zip( + b: Option, + c: Option, + d: Option, + e: Option, + map: (A, B, C, D, E) -> F +): Option { + contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } + return zip(b, c, d, e, someUnit, someUnit, someUnit, someUnit, someUnit) { a, b, c, d, e, f, _, _, _, _ -> + map( + a, + b, + c, + d, + e + ) } +} - /** - * The given function is applied as a fire and forget effect - * if this is a `some`. - * When applied the result is ignored and the original - * Some value is returned - * - * Example: - * ```kotlin - * import arrow.core.Some - * import arrow.core.none - * - * fun main() { - * Some(12).tap { println("flower") } // Result: prints "flower" and returns: Some(12) - * none().tap { println("flower") } // Result: None - * } - * ``` - * - */ - public inline fun tap(f: (A) -> Unit): Option { - contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is None -> this - is Some -> { - f(this.value) - this - } - } +public inline fun Option.zip( + b: Option, + c: Option, + d: Option, + e: Option, + f: Option, + map: (A, B, C, D, E, F) -> G +): Option { + contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } + return zip(b, c, d, e, f, someUnit, someUnit, someUnit, someUnit) { a, b, c, d, e, f, _, _, _, _ -> + map( + a, + b, + c, + d, + e, + f + ) } +} - public inline fun zip( - b: Option, - c: Option, - d: Option, - map: (A, B, C, D) -> E - ): Option { - contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } - return zip( +public inline fun Option.zip( + b: Option, + c: Option, + d: Option, + e: Option, + f: Option, + g: Option, + map: (A, B, C, D, E, F, G) -> H +): Option { + contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } + return zip(b, c, d, e, f, g, someUnit, someUnit, someUnit) { a, b, c, d, e, f, g, _, _, _ -> + map( + a, b, c, d, - Some.unit, - Some.unit, - Some.unit, - Some.unit, - Some.unit, - Some.unit - ) { a, b, c, d, _, _, _, _, _, _ -> map(a, b, c, d) } + e, + f, + g + ) } +} - public inline fun zip( - b: Option, - c: Option, - d: Option, - e: Option, - map: (A, B, C, D, E) -> F - ): Option { - contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } - return zip(b, c, d, e, Some.unit, Some.unit, Some.unit, Some.unit, Some.unit) { a, b, c, d, e, f, _, _, _, _ -> - map( - a, - b, - c, - d, - e - ) - } +public inline fun Option.zip( + b: Option, + c: Option, + d: Option, + e: Option, + f: Option, + g: Option, + h: Option, + map: (A, B, C, D, E, F, G, H) -> I +): Option { + contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } + return zip(b, c, d, e, f, g, h, someUnit, someUnit) { a, b, c, d, e, f, g, h, _, _ -> + map( + a, + b, + c, + d, + e, + f, + g, + h + ) } +} + +public inline fun Option.zip( + b: Option, + c: Option, + d: Option, + e: Option, + f: Option, + g: Option, + h: Option, + i: Option, + map: (A, B, C, D, E, F, G, H, I) -> J +): Option { + contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } + return zip(b, c, d, e, f, g, h, i, someUnit) { a, b, c, d, e, f, g, h, i, _ -> map(a, b, c, d, e, f, g, h, i) } +} - public inline fun zip( - b: Option, - c: Option, - d: Option, - e: Option, - f: Option, - map: (A, B, C, D, E, F) -> G - ): Option { - contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } - return zip(b, c, d, e, f, Some.unit, Some.unit, Some.unit, Some.unit) { a, b, c, d, e, f, _, _, _, _ -> - map( - a, - b, - c, - d, - e, - f - ) +public inline fun Option.zip( + b: Option, + c: Option, + d: Option, + e: Option, + f: Option, + g: Option, + h: Option, + i: Option, + j: Option, + map: (A, B, C, D, E, F, G, H, I, J) -> K +): Option { + contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } + val a = + this.getOrElse { + return None } - } + val b = + b.getOrElse { + return None + } + val c = + c.getOrElse { + return None + } + val d = + d.getOrElse { + return None + } + val e = + e.getOrElse { + return None + } + val f = + f.getOrElse { + return None + } + val g = + g.getOrElse { + return None + } + val h = + h.getOrElse { + return None + } + val i = + i.getOrElse { + return None + } + val j = + j.getOrElse { + return None + } + return Some(map(a, b, c, d, e, f, g, h, i, j)) +} - public inline fun zip( - b: Option, - c: Option, - d: Option, - e: Option, - f: Option, - g: Option, - map: (A, B, C, D, E, F, G) -> H - ): Option { - contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } - return zip(b, c, d, e, f, g, Some.unit, Some.unit, Some.unit) { a, b, c, d, e, f, g, _, _, _ -> map(a, b, c, d, e, f, g) } - } +/** + * Returns true if the option is [None], false otherwise. + * @note Used only for performance instead of fold. + */ +public fun Option.isEmpty(): Boolean = this == None - public inline fun zip( - b: Option, - c: Option, - d: Option, - e: Option, - f: Option, - g: Option, - h: Option, - map: (A, B, C, D, E, F, G, H) -> I - ): Option { - contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } - return zip(b, c, d, e, f, g, h, Some.unit, Some.unit) { a, b, c, d, e, f, g, h, _, _ -> map(a, b, c, d, e, f, g, h) } - } +public fun Option.isNotEmpty(): Boolean = !isEmpty() - public inline fun zip( - b: Option, - c: Option, - d: Option, - e: Option, - f: Option, - g: Option, - h: Option, - i: Option, - map: (A, B, C, D, E, F, G, H, I) -> J - ): Option { - contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } - return zip(b, c, d, e, f, g, h, i, Some.unit) { a, b, c, d, e, f, g, h, i, _ -> map(a, b, c, d, e, f, g, h, i) } - } +/** + * alias for [isDefined] + */ +public fun Option.nonEmpty(): Boolean = isDefined() - public inline fun zip( - b: Option, - c: Option, - d: Option, - e: Option, - f: Option, - g: Option, - h: Option, - i: Option, - j: Option, - map: (A, B, C, D, E, F, G, H, I, J) -> K - ): Option { - contract { callsInPlace(map, InvocationKind.AT_MOST_ONCE) } - return if (this is Some && b is Some && c is Some && d is Some && e is Some && f is Some && g is Some && h is Some && i is Some && j is Some) { - Some(map(this.value, b.value, c.value, d.value, e.value, f.value, g.value, h.value, i.value, j.value)) - } else { - None - } - } +/** + * Returns true if the option is an instance of [Some], false otherwise. + * @note Used only for performance instead of fold. + */ +public fun Option.isDefined(): Boolean = !isEmpty() - /** - * Returns true if the option is [None], false otherwise. - * @note Used only for performance instead of fold. - */ - public abstract fun isEmpty(): Boolean - - public fun isNotEmpty(): Boolean = !isEmpty() - - /** - * alias for [isDefined] - */ - public fun nonEmpty(): Boolean = isDefined() - - /** - * Returns true if the option is an instance of [Some], false otherwise. - * @note Used only for performance instead of fold. - */ - public fun isDefined(): Boolean = !isEmpty() - - public fun orNull(): A? { - contract { - returns(null) implies (this@Option is None) - returnsNotNull() implies (this@Option is Some) - } - return fold({ null }, ::identity) - } +public inline fun Option.orNull(): A? { + return fold({ null }, ::identity) +} - /** - * Returns a [Some<$B>] containing the result of applying $f to this $option's - * value if this $option is nonempty. Otherwise return $none. - * - * @note This is similar to `flatMap` except here, - * $f does not need to wrap its result in an $option. - * - * @param f the function to apply - * @see flatMap - */ - public inline fun map(f: (A) -> B): Option { - contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } - return flatMap { a -> Some(f(a)) } - } +/** + * Returns a [Some<$B>] containing the result of applying $f to this $option's + * value if this $option is nonempty. Otherwise return $none. + * + * @note This is similar to `flatMap` except here, + * $f does not need to wrap its result in an $option. + * + * @param f the function to apply + * @see flatMap + */ +public inline fun Option.map(f: (A) -> B): Option { + contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } + return flatMap { a -> Some(f(a)) } +} - public inline fun fold(ifEmpty: () -> R, ifSome: (A) -> R): R { - contract { - callsInPlace(ifEmpty, InvocationKind.AT_MOST_ONCE) - callsInPlace(ifSome, InvocationKind.AT_MOST_ONCE) - } - return when (this) { - is None -> ifEmpty() - is Some -> ifSome(value) +@OptIn(OptionInternals::class) +public inline fun Option.fold(ifEmpty: () -> R, ifSome: (A) -> R): R { + contract { + callsInPlace(ifEmpty, InvocationKind.AT_MOST_ONCE) + callsInPlace(ifSome, InvocationKind.AT_MOST_ONCE) + } + return when (this) { + None -> ifEmpty() + else -> { + val value = value + if (isTypeOption() && value != null) Option.construct(value).invokeBlockOnOption(ifSome) + else ifSome(value) } } +} - /** - * Returns $none if the result of applying $f to this $option's value is null. - * Otherwise returns the result. - * - * @note This is similar to `.flatMap { Option.fromNullable(null)) }` - * and primarily for convenience. - * - * @param f the function to apply. - * */ - public inline fun mapNotNull(f: (A) -> B?): Option { - contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } - return flatMap { a -> fromNullable(f(a)) } - } +/** + * This function is used to ensure that an Option doesn't get boxed when it gets passed + * to the $block. It unsafely casts it inside, but because it is inline, the compiler ultimately doesn't box + * the Option. + */ +@PublishedApi +internal inline fun Option<*>.invokeBlockOnOption(block: (A) -> R): R { + return block(this as A) +} - /** - * Returns the result of applying $f to this $option's value if - * this $option is nonempty. - * Returns $none if this $option is empty. - * Slightly different from `map` in that $f is expected to - * return an $option (which could be $none). - * - * @param f the function to apply - * @see map - */ - public inline fun flatMap(f: (A) -> Option): Option { - contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is None -> this - is Some -> f(value) - } - } +/** + * Returns $none if the result of applying $f to this $option's value is null. + * Otherwise returns the result. + * + * @note This is similar to `.flatMap { Option.fromNullable(null)) }` + * and primarily for convenience. + * + * @param f the function to apply. + * */ +public inline fun Option.mapNotNull(f: (A) -> B?): Option { + contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } + return flatMap { a -> fromNullable(f(a)) } +} - /** - * Align two options (`this` on the left and [b] on the right) as one Option of [Ior]. - */ - public infix fun align(b: Option): Option> = - when (this) { - None -> when (b) { - None -> None - is Some -> Some(b.value.rightIor()) - } +/** + * Returns the result of applying $f to this $option's value if + * this $option is nonempty. + * Returns $none if this $option is empty. + * Slightly different from `map` in that $f is expected to + * return an $option (which could be $none). + * + * @param f the function to apply + * @see map + */ +public inline fun Option.flatMap(f: (A) -> Option): Option { + contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } + return fold({ None }, f) +} - is Some -> when (b) { - None -> Some(this.value.leftIor()) - is Some -> Some(Pair(this.value, b.value).bothIor()) - } - } +/** + * Align two options (`this` on the left and [b] on the right) as one Option of [Ior]. + */ +public inline infix fun Option.align(b: Option): Option> = + fold({ + b.fold({ None }) { Some(it.rightIor()) } + }, + { a -> + b.fold({ + Some(a.leftIor()) + }, + { + Some(Pair(a, it).bothIor()) + }) + }) + +/** + * Align two options (`this` on the left and [b] on the right) as one Option of [Ior], and then, if it's not [None], map it using [f]. + * + * @note This function works like a regular `align` function, but is then mapped by the `map` function. + */ +public inline fun Option.align(b: Option, f: (Ior) -> C): Option { + contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } + return align(b).map(f) +} - /** - * Align two options (`this` on the left and [b] on the right) as one Option of [Ior], and then, if it's not [None], map it using [f]. - * - * @note This function works like a regular `align` function, but is then mapped by the `map` function. - */ - public inline fun align(b: Option, f: (Ior) -> C): Option { - contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } - return align(b).map(f) +/** + * Returns true if this option is empty '''or''' the predicate + * $predicate returns true when applied to this $option's value. + * + * @param predicate the predicate to test + */ +public inline fun Option.all(predicate: (A) -> Boolean): Boolean { + contract { callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) } + return fold({ true }, predicate) +} + +public inline fun Option.crosswalk(f: (A) -> Option): Option> = + fold({ None }, { f(it).map(::Some) }) + +public inline fun Option.crosswalkMap(f: (A) -> Map): Map> = + fold({ emptyMap() }) { value -> + f(value).mapValues { Some(it.value) } } - /** - * Returns true if this option is empty '''or''' the predicate - * $predicate returns true when applied to this $option's value. - * - * @param predicate the predicate to test - */ - public inline fun all(predicate: (A) -> Boolean): Boolean { - contract { callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) } - return fold({ true }, predicate) +public inline fun Option.crosswalkNull(f: (A) -> B?): Option? = + fold({ null }) { + f(it)?.let(::Some) } - public inline fun crosswalk(f: (A) -> Option): Option> = - when (this) { - is None -> this - is Some -> f(value).map { Some(it) } - } +/** + * Returns this $option if it is nonempty '''and''' applying the predicate $p to + * this $option's value returns true. Otherwise, return $none. + * + * @param predicate the predicate used for testing. + */ +public inline fun Option.filter(predicate: (A) -> Boolean): Option { + contract { callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) } + return flatMap { a -> if (predicate(a)) Some(a) else None } +} - public inline fun crosswalkMap(f: (A) -> Map): Map> = - when (this) { - is None -> emptyMap() - is Some -> f(value).mapValues { Some(it.value) } - } +/** + * Returns this $option if it is nonempty '''and''' applying the predicate $p to + * this $option's value returns false. Otherwise, return $none. + * + * @param predicate the predicate used for testing. + */ +public inline fun Option.filterNot(predicate: (A) -> Boolean): Option { + contract { callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) } + return flatMap { a -> if (!predicate(a)) Some(a) else None } +} - public inline fun crosswalkNull(f: (A) -> B?): Option? = - when (this) { - is None -> null - is Some -> f(value)?.let { Some(it) } - } +/** + * Returns true if this option is nonempty '''and''' the predicate + * $p returns true when applied to this $option's value. + * Otherwise, returns false. + * + * Example: + * ```kotlin + * import arrow.core.Some + * import arrow.core.None + * import arrow.core.Option + * import arrow.core.exists + * + * fun main() { + * Some(12).exists { it > 10 } // Result: true + * Some(7).exists { it > 10 } // Result: false + * + * val none: Option = None + * none.exists { it > 10 } // Result: false + * } + * ``` + * + * + * @param predicate the predicate to test + */ +public inline fun Option.exists(predicate: (A) -> Boolean): Boolean { + contract { callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) } + return fold({ false }, predicate) +} - /** - * Returns this $option if it is nonempty '''and''' applying the predicate $p to - * this $option's value returns true. Otherwise, return $none. - * - * @param predicate the predicate used for testing. - */ - public inline fun filter(predicate: (A) -> Boolean): Option { - contract { callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) } - return flatMap { a -> if (predicate(a)) Some(a) else None } +/** + * Returns the $option's value if this option is nonempty '''and''' the predicate + * $p returns true when applied to this $option's value. + * Otherwise, returns null. + * + * Example: + * ```kotlin + * import arrow.core.Some + * import arrow.core.None + * import arrow.core.Option + * import arrow.core.exists + * + * fun main() { + * Some(12).exists { it > 10 } // Result: 12 + * Some(7).exists { it > 10 } // Result: null + * + * val none: Option = None + * none.exists { it > 10 } // Result: null + * } + * ``` + * + */ +public inline fun Option.findOrNull(predicate: (A) -> Boolean): A? { + contract { callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) } + return fold({ null }) { + it.takeIf(predicate) } +} - /** - * Returns this $option if it is nonempty '''and''' applying the predicate $p to - * this $option's value returns false. Otherwise, return $none. - * - * @param predicate the predicate used for testing. - */ - public inline fun filterNot(predicate: (A) -> Boolean): Option { - contract { callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) } - return flatMap { a -> if (!predicate(a)) Some(a) else None } +public inline fun Option.foldMap(MB: Monoid, f: (A) -> B): B = MB.run { + foldLeft(empty()) { b, a -> b.combine(f(a)) } +} + +public inline fun Option.foldLeft(initial: B, operation: (B, A) -> B): B = + fold({ initial }) { + operation(initial, it) } - /** - * Returns true if this option is nonempty '''and''' the predicate - * $p returns true when applied to this $option's value. - * Otherwise, returns false. - * - * Example: - * ```kotlin - * import arrow.core.Some - * import arrow.core.None - * import arrow.core.Option - * - * fun main() { - * Some(12).exists { it > 10 } // Result: true - * Some(7).exists { it > 10 } // Result: false - * - * val none: Option = None - * none.exists { it > 10 } // Result: false - * } - * ``` - * - * - * @param predicate the predicate to test - */ - public inline fun exists(predicate: (A) -> Boolean): Boolean { - contract { callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) } - return fold({ false }, predicate) +public inline fun Option.padZip(other: Option): Option> = + align(other) { ior -> + ior.fold( + { it to null }, + { null to it }, + { a, b -> a to b } + ) } - /** - * Returns the $option's value if this option is nonempty '''and''' the predicate - * $p returns true when applied to this $option's value. - * Otherwise, returns null. - * - * Example: - * ```kotlin - * import arrow.core.Some - * import arrow.core.None - * import arrow.core.Option - * - * fun main() { - * Some(12).exists { it > 10 } // Result: 12 - * Some(7).exists { it > 10 } // Result: null - * - * val none: Option = None - * none.exists { it > 10 } // Result: null - * } - * ``` - * - */ - public inline fun findOrNull(predicate: (A) -> Boolean): A? { - contract { callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Some -> if (predicate(value)) value else null - is None -> null - } +public inline fun Option.padZip(other: Option, f: (A?, B?) -> C): Option = + align(other) { ior -> + ior.fold( + { f(it, null) }, + { f(null, it) }, + { a, b -> f(a, b) } + ) } - public inline fun foldMap(MB: Monoid, f: (A) -> B): B = MB.run { - foldLeft(empty()) { b, a -> b.combine(f(a)) } +public inline fun Option.reduceOrNull(initial: (A) -> B, operation: (acc: B, A) -> B): B? = + fold({ null }) { + operation(initial(it), it) } - public inline fun foldLeft(initial: B, operation: (B, A) -> B): B = - when (this) { - is Some -> operation(initial, value) - is None -> initial - } +public inline fun Option.replicate(n: Int): Option> = + if (n <= 0) Some(emptyList()) else map { a -> List(n) { a } } - public fun padZip(other: Option): Option> = - align(other) { ior -> - ior.fold( - { it to null }, - { null to it }, - { a, b -> a to b } - ) - } +public inline fun Option.toEither(ifEmpty: () -> L): Either { + contract { callsInPlace(ifEmpty, InvocationKind.AT_MOST_ONCE) } + return fold({ ifEmpty().left() }, { it.right() }) +} - public inline fun padZip(other: Option, f: (A?, B?) -> C): Option = - align(other) { ior -> - ior.fold( - { f(it, null) }, - { f(null, it) }, - { a, b -> f(a, b) } - ) - } +public inline fun Option.toList(): List = fold(::emptyList) { listOf(it) } - public inline fun reduceOrNull(initial: (A) -> B, operation: (acc: B, A) -> B): B? = - when (this) { - is None -> null - is Some -> operation(initial(value), value) - } +public fun Option.void(): Option = + someUnit - public fun replicate(n: Int): Option> = - if (n <= 0) Some(emptyList()) else map { a -> List(n) { a } } +public inline fun Option.pairLeft(left: L): Option> = this.map { left to it } - public inline fun toEither(ifEmpty: () -> L): Either { - contract { callsInPlace(ifEmpty, InvocationKind.AT_MOST_ONCE) } - return fold({ ifEmpty().left() }, { it.right() }) - } +public inline fun Option.pairRight(right: R): Option> = this.map { it to right } - public fun toList(): List = fold(::emptyList) { listOf(it) } +public infix fun Option.and(value: Option): Option = if (isEmpty()) { + None +} else { + value +} - public fun void(): Option = - map { Unit } +@OptIn(OptionInternals::class) +public val None: Option = Option.construct(NestedNone(0U)) - public fun pairLeft(left: L): Option> = this.map { left to it } +@PublishedApi +internal data class NestedNone internal constructor(val nesting: UInt) { + companion object { + private val cache = Array(22) { NestedNone(it.toUInt()) } + operator fun invoke(nesting: UInt) = + cache.getOrElse(nesting.toInt()) { NestedNone(it.toUInt()) } + } - public fun pairRight(right: R): Option> = this.map { it to right } + override fun toString(): String = + nesting.toInt().coerceAtLeast(0).let { nesting -> + buildString("Option.Some()".length * nesting + "Option.None".length) { + repeat(nesting) { this.append("Option.Some(") } + append("Option.None") + repeat(nesting) { this.append(")") } + } + } +} - public infix fun and(value: Option): Option = if (isEmpty()) { - None - } else { - value +@PublishedApi +internal data class NestedNull internal constructor(val nesting: UInt) { + companion object { + private val cache = Array(22) { NestedNull(it.toUInt()) } + operator fun invoke(nesting: UInt) = + cache.getOrElse(nesting.toInt()) { NestedNull(it.toUInt()) } } - override fun toString(): String = fold( - { "Option.None" }, - { "Option.Some($it)" } - ) + override fun toString(): String = + nesting.toInt().coerceAtLeast(0).let { nesting -> + buildString("Option.Some()".length * nesting + "null".length) { + repeat(nesting) { this.append("Option.Some(") } + append("null") + repeat(nesting) { this.append(")") } + } + } } -public object None : Option() { - override fun isEmpty(): Boolean = true - - override fun toString(): String = "Option.None" +@OptIn(OptionInternals::class) +public inline fun Some(value: T): Option { + // This `is` check gets inlined by the compiler if `value` is statically known to be an Option + val trueValue = if (value is Option<*>) value.underlying else value + return _Some(if (isTypeOption()) trueValue else value) } -public data class Some(val value: T) : Option() { - override fun isEmpty(): Boolean = false +@OptionInternals +@PublishedApi +internal fun _Some(trueValue: Any?): Option = + Option.construct( + when (trueValue) { + is NestedNone -> NestedNone(trueValue.nesting + 1U) + is NestedNull -> NestedNull(trueValue.nesting + 1U) + else -> trueValue ?: NestedNull(0U) + } + ) - override fun toString(): String = "Option.Some($value)" +@PublishedApi +internal val someUnit: Option = Some(Unit) + +@PublishedApi +internal val boxedSomeUnit: Any = someUnit - public companion object { - @PublishedApi - internal val unit: Option = Some(Unit) - } -} /** * Returns the option's value if the option is nonempty, otherwise @@ -876,7 +990,7 @@ public data class Some(val value: T) : Option() { * * @param default the default expression. */ -public inline fun Option.getOrElse(default: () -> T): T { +public inline fun Option.getOrElse(default: () -> T): T { contract { callsInPlace(default, InvocationKind.AT_MOST_ONCE) } return fold({ default() }, ::identity) } @@ -898,9 +1012,9 @@ public infix fun Option.or(value: Option): Option = if (isEmpty()) this } -public fun T?.toOption(): Option = this?.let { Some(it) } ?: None +public inline fun T?.toOption(): Option = this?.let { Some(it) } ?: None -public inline fun Boolean.maybe(f: () -> A): Option { +public inline fun Boolean.maybe(f: () -> A): Option { contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } return if (this) { Some(f()) @@ -909,7 +1023,7 @@ public inline fun Boolean.maybe(f: () -> A): Option { } } -public fun A.some(): Option = Some(this) +public inline fun A.some(): Option = Some(this) public fun none(): Option = None @@ -918,23 +1032,20 @@ public fun Iterable>.combineAll(MA: Monoid): Option = fold(Monoid.option(MA)) @Deprecated("use getOrElse instead", ReplaceWith("getOrElse { MA.empty() }")) -public fun Option.combineAll(MA: Monoid): A = +public inline fun Option.combineAll(MA: Monoid): A = getOrElse { MA.empty() } -public inline fun Option.ensure(error: () -> Unit, predicate: (A) -> Boolean): Option { +public inline fun Option.ensure(error: () -> Unit, predicate: (A) -> Boolean): Option { contract { callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) callsInPlace(error, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Some -> - if (predicate(value)) this - else { - error() - None - } - - is None -> this + return fold({ None }) { + if (predicate(it)) this + else { + error() + None + } } } @@ -949,7 +1060,7 @@ public inline fun Option<*>.filterIsInstance(): Option = } } -public inline fun Option.handleError(f: (Unit) -> A): Option { +public inline fun Option.handleError(f: (Unit) -> A): Option { contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) } return handleErrorWith { Some(f(Unit)) } } @@ -962,7 +1073,7 @@ public inline fun Option.handleErrorWith(f: (Unit) -> Option): Option< public fun Option>.flatten(): Option = flatMap(::identity) -public inline fun Option.redeem(fe: (Unit) -> B, fb: (A) -> B): Option { +public inline fun Option.redeem(fe: (Unit) -> B, fb: (A) -> B): Option { contract { callsInPlace(fe, InvocationKind.AT_MOST_ONCE) callsInPlace(fb, InvocationKind.AT_MOST_ONCE) @@ -970,7 +1081,7 @@ public inline fun Option.redeem(fe: (Unit) -> B, fb: (A) -> B): Option return map(fb).handleError(fe) } -public inline fun Option.redeemWith(fe: (Unit) -> Option, fb: (A) -> Option): Option { +public inline fun Option.redeemWith(fe: (Unit) -> Option, fb: (A) -> Option): Option { contract { callsInPlace(fe, InvocationKind.AT_MOST_ONCE) callsInPlace(fb, InvocationKind.AT_MOST_ONCE) @@ -978,15 +1089,15 @@ public inline fun Option.redeemWith(fe: (Unit) -> Option, fb: (A) - return flatMap(fb).handleErrorWith(fe) } -public fun Option.replicate(n: Int, MA: Monoid): Option = MA.run { +public inline fun Option.replicate(n: Int, MA: Monoid): Option = MA.run { if (n <= 0) Some(empty()) else map { a -> List(n) { a }.fold(empty()) { acc, v -> acc + v } } } -public fun Option>.rethrow(): Option = +public inline fun Option>.rethrow(): Option = flatMap { it.fold({ None }, { a -> Some(a) }) } -public fun Option.salign(SA: Semigroup, b: Option): Option = +public inline fun Option.salign(SA: Semigroup, b: Option): Option = align(b) { it.fold(::identity, ::identity) { a, b -> SA.run { a.combine(b) } @@ -999,39 +1110,38 @@ public fun Option.salign(SA: Semigroup, b: Option): Option = * @receiver Option of Either * @return a tuple containing Option of [Either.Left] and another Option of its [Either.Right] value. */ -public fun Option>.separateEither(): Pair, Option> { +public inline fun Option>.separateEither(): Pair, Option> { val asep = flatMap { gab -> gab.fold({ Some(it) }, { None }) } val bsep = flatMap { gab -> gab.fold({ None }, { Some(it) }) } return asep to bsep } -public fun Option>.unalign(): Pair, Option> = +public inline fun Option>.unalign(): Pair, Option> = unalign(::identity) -public inline fun Option.unalign(f: (C) -> Ior): Pair, Option> = - when (val option = this.map(f)) { - is None -> None to None - is Some -> when (val v = option.value) { +public inline fun Option.unalign(f: (C) -> Ior): Pair, Option> = + map(f).fold({ None to None }) { + when (val v = it) { is Ior.Left -> Some(v.value) to None is Ior.Right -> None to Some(v.value) is Ior.Both -> Some(v.leftValue) to Some(v.rightValue) } } -public fun Option>.unite(MA: Monoid): Option = +public inline fun Option>.unite(MA: Monoid): Option = map { iterable -> iterable.fold(MA) } -public fun Option>.uniteEither(): Option = +public inline fun Option>.uniteEither(): Option = flatMap { either -> either.fold({ None }, { b -> Some(b) }) } -public fun Option>.unzip(): Pair, Option> = +public inline fun Option>.unzip(): Pair, Option> = unzip(::identity) -public inline fun Option.unzip(f: (C) -> Pair): Pair, Option> = +public inline fun Option.unzip(f: (C) -> Pair): Pair, Option> = fold( { None to None }, { f(it).let { pair -> Some(pair.first) to Some(pair.second) } } @@ -1062,17 +1172,14 @@ public fun Option.widen(): Option = public fun Option>.toMap(): Map = this.toList().toMap() -public fun Option.combine(SGA: Semigroup, b: Option): Option = - when (this) { - is Some -> when (b) { - is Some -> Some(SGA.run { value.combine(b.value) }) - None -> this +public inline fun Option.combine(SGA: Semigroup, b: Option): Option = + fold({ b }) { a -> + b.fold({ this }) { b -> + Some(SGA.run { a.combine(b) }) } - - None -> b } -public operator fun > Option.compareTo(other: Option): Int = fold( +public inline operator fun > Option.compareTo(other: Option): Int = fold( { other.fold({ 0 }, { -1 }) }, { a1 -> other.fold({ 1 }, { a2 -> a1.compareTo(a2) }) @@ -1123,8 +1230,5 @@ public operator fun > Option.compareTo(other: Option): I * * */ -public inline fun Option.recover(recover: OptionRaise.(None) -> A): Option = - when (this@recover) { - is None -> option { recover(this, None) } - is Some -> this@recover - } +public inline fun Option.recover(recover: OptionRaise.(Option) -> A): Option = + fold({ option { recover(this, None) } }) { this } 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 fc2ffe650b8..3221bae8589 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 @@ -765,5 +765,5 @@ public fun Sequence.void(): Sequence = public fun Sequence.widen(): Sequence = this -public fun Sequence>.filterOption(): Sequence = +public inline fun Sequence>.filterOption(): Sequence = mapNotNull { it.orNull() } diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/map.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/map.kt index 2dadbc549a7..c6f81bb5029 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/map.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/map.kt @@ -246,7 +246,7 @@ public fun Map.filterMap(f: (A) -> B?): Map { return destination } -public fun Map>.filterOption(): Map = filterMap { it.orNull() } +public inline fun Map>.filterOption(): Map = filterMap { it.orNull() } /** * Returns a Map containing all elements that are instances of specified type parameter R. @@ -421,7 +421,7 @@ public fun Map>.unzip(): Pair, Map> = public fun Map.unzip(fc: (Map.Entry) -> Pair): Pair, Map> = mapValues(fc).unzip() -public fun Map.getOrNone(key: K): Option = this[key].toOption() +public inline fun Map.getOrNone(key: K): Option = this[key].toOption() public fun Map.combine(SG: Semigroup, b: Map): Map = with(SG) { if (size < b.size) foldLeft(b) { my, (k, b) -> my + Pair(k, b.maybeCombine(my[k])) } diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt index 28bf57b41b7..0e0bf8dd9a5 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt @@ -1,18 +1,12 @@ @file:JvmMultifileClass @file:JvmName("RaiseKt") @file:OptIn(ExperimentalTypeInference::class, ExperimentalContracts::class) + package arrow.core.raise import arrow.atomic.Atomic import arrow.atomic.updateAndGet -import arrow.core.Either -import arrow.core.Ior -import arrow.core.None -import arrow.core.Option -import arrow.core.Some -import arrow.core.getOrElse -import arrow.core.identity -import arrow.core.orElse +import arrow.core.* import arrow.typeclasses.Semigroup import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind.EXACTLY_ONCE @@ -37,7 +31,7 @@ public inline fun result(block: ResultRaise.() -> A): Result { return fold({ block(ResultRaise(this)) }, Result.Companion::failure, Result.Companion::success) } -public inline fun option(block: OptionRaise.() -> A): Option { +public inline fun option(block: OptionRaise.() -> A): Option { contract { callsInPlace(block, EXACTLY_ONCE) } return fold({ block(OptionRaise(this)) }, ::identity, ::Some) } @@ -48,8 +42,8 @@ public inline fun ior(semigroup: Semigroup, @BuilderInference block: I return fold>( { block(IorRaise(semigroup, state, this)) }, { e -> throw e }, - { e -> Ior.Left(state.get().getOrElse { e }) }, - { a -> state.get().fold({ Ior.Right(a) }, { Ior.Both(it, a) }) } + { e -> Ior.Left(state.get().getOrElse { e } as E) }, + { a -> state.get().fold>({ Ior.Right(a) }, { Ior.Both(it as E, a) }) } ) } @@ -60,13 +54,13 @@ public value class NullableRaise(private val cont: Raise) : Raise { @RaiseDSL public fun ensure(value: Boolean): Unit = ensure(value) { null } override fun raise(r: Nothing?): Nothing = cont.raise(r) - public fun Option.bind(): B = bind { raise(null) } - + public inline fun Option.bind(): B = bind { raise(null) } + public fun B?.bind(): B { contract { returns() implies (this@bind != null) } return this ?: raise(null) } - + public fun ensureNotNull(value: B?): B { contract { returns() implies (value != null) } return ensureNotNull(value) { null } @@ -80,11 +74,11 @@ public value class ResultRaise(private val cont: Raise) : Raise) : Raise { - override fun raise(r: None): Nothing = cont.raise(r) - public fun Option.bind(): B = bind { raise(None) } +public value class OptionRaise(private val cont: Raise>) : Raise> { + override fun raise(r: Option): Nothing = cont.raise(r) + public inline fun Option.bind(): B = bind { raise(None) } public fun ensure(value: Boolean): Unit = ensure(value) { None } - + public fun ensureNotNull(value: B?): B { contract { returns() implies (value != null) } return ensureNotNull(value) { None } @@ -96,9 +90,9 @@ public class IorRaise @PublishedApi internal constructor( private val state: Atomic>, private val raise: Raise, ) : Raise, Semigroup by semigroup { - + override fun raise(r: E): Nothing = raise.raise(combine(r)) - + public fun Ior.bind(): B = when (this) { is Ior.Left -> raise(value) @@ -108,9 +102,16 @@ public class IorRaise @PublishedApi internal constructor( rightValue } } - + + // We're using Some here so that if E happens to be an Option, we store it as is + // i.e. with no denesting. This is so that when we map over it for instance, we have no idea + // whether E is an option or not, so we use Some instead of risking a CCE. + // We also must use Any? as the type parameter for map and getOrElse because we don't have the + // reified E type. We have to be careful though, since if someone else had access to our $state + // and called getOrElse on it with a reified E, and E happened to be an Option, they'd end up getting + // an Option>, where unpacking that option would result in a CCE when the value is used as an X. private fun combine(other: E): E = state.updateAndGet { prev -> - prev.map { e -> e.combine(other) }.orElse { Some(other) } - }.getOrElse { other } + prev.map { e -> (e as E).combine(other) }.orElse { Some(other) } as Option + }.getOrElse { other } as E } diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Effect.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Effect.kt index 3a975ff4f5c..54f3b662ba3 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Effect.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Effect.kt @@ -159,7 +159,7 @@ import kotlin.jvm.JvmName * suspend fun Effect.toEither(): Either = * fold({ Either.Left(it) }) { Either.Right(it) } * - * suspend fun Effect.toOption(): Option = + * suspend inline fun Effect, A>.toOption(): Option = * fold(::identity) { Some(it) } * ``` * diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Mappers.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Mappers.kt index 8a133ed3c99..7f39081fbfe 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Mappers.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Mappers.kt @@ -24,12 +24,12 @@ public suspend fun Effect.orNull(): A? = fold({ _: E -> null }) { i public fun EagerEffect.orNull(): A? = fold({ _: E -> null }) { it } /** Run the [Effect] by returning [Option] of [A], [orElse] run the fallback lambda and returning its result of [Option] of [A]. */ -public suspend fun Effect.toOption(orElse: suspend (E) -> Option): Option = fold(orElse) { Some(it) } -public fun EagerEffect.toOption(orElse: (E) -> Option): Option = fold(orElse) { Some(it) } +public suspend inline fun Effect.toOption(noinline orElse: suspend (E) -> Option): Option = fold(orElse) { Some(it) } +public inline fun EagerEffect.toOption(orElse: (E) -> Option): Option = fold(orElse) { Some(it) } /** Run the [Effect] by returning [Option] of [A], or [None] if raised with [None]. */ -public suspend fun Effect.toOption(): Option = option { invoke() } -public fun EagerEffect.toOption(): Option = option { invoke() } +public suspend inline fun Effect, A>.toOption(): Option = option { invoke() } +public inline fun EagerEffect, A>.toOption(): Option = option { invoke() } /** Run the [Effect] by returning [Result] of [A], [orElse] run the fallback lambda and returning its result of [Result] of [A]. */ public suspend fun Effect.toResult(orElse: suspend (E) -> Result): Result = diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Raise.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Raise.kt index b851cd5bfc3..9a4661b6ea2 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Raise.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Raise.kt @@ -3,11 +3,7 @@ @file:JvmName("RaiseKt") package arrow.core.raise -import arrow.core.Either -import arrow.core.None -import arrow.core.Option -import arrow.core.Some -import arrow.core.identity +import arrow.core.* import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind.AT_MOST_ONCE import kotlin.contracts.InvocationKind.EXACTLY_ONCE @@ -202,46 +198,6 @@ public interface Raise { public fun Result.bind(transform: (Throwable) -> R): A = fold(::identity) { throwable -> raise(transform(throwable)) } - /** - * Extract the [Some] value out of [Option], - * because [Option] works with [None] as its error type you need to [transform] [None] to [R]. - * - * Note that this functions can currently not be _inline_ without Context Receivers, - * and thus doesn't allow suspension in its error handler. - * To do so, use [Option.recover] and [bind]. - * - * - * ```kotlin - * suspend fun test() { - * val empty: Option = None - * either { - * val x: Int = empty.bind { _: None -> 1 } - * val y: Int = empty.bind { _: None -> raise("Something bad happened: Boom!") } - * val z: Int = empty.recover { _: None -> - * delay(10) - * 1 - * }.bind { raise("Something bad happened: Boom!") } - * x + y + z - * } shouldBe Either.Left("Something bad happened: Boom!") - * } - * ``` - * - * - */ - public fun Option.bind(transform: Raise.(None) -> A): A = - when (this) { - None -> transform(None) - is Some -> value - } - @RaiseDSL public suspend infix fun Effect.recover(@BuilderInference resolve: suspend Raise.(E) -> A): A = recover({ invoke() }) { resolve(it) } @@ -276,6 +232,41 @@ public interface Raise { ): A = fold({ catch(it) }, { raise(it) }, { it }) } +/** + * Extract the [Some] value out of [Option], + * because [Option] works with [None] as its error type you need to [transform] [None] to [R]. + * + * + * + * ```kotlin + * suspend fun test() { + * val empty: Option = None + * either { + * val x: Int = empty.bind { _: Option -> 1 } + * val y: Int = empty.bind { _: Option -> raise("Something bad happened: Boom!") } + * val z: Int = empty.recover { _: Option -> + * delay(10) + * 1 + * }.bind { raise("Something bad happened: Boom!") } + * x + y + z + * } shouldBe Either.Left("Something bad happened: Boom!") + * } + * ``` + * + * + */ +public inline fun Option.bind(transform: (Option) -> A): A = + getOrElse { transform(None) } + /** * Execute the [Raise] context function resulting in [A] or any _logical error_ of type [E], * and recover by providing a fallback value of type [A] or raising a new error of type [R]. diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Monoid.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Monoid.kt index 0490d94fdd6..499f7866c79 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Monoid.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Monoid.kt @@ -105,10 +105,10 @@ public interface Monoid : Semigroup { ) : Monoid> { override fun append(a: Option, b: Option): Option = - a.combine(MA, b) + a.combine(MA as Semigroup, b) as Option override fun Option.maybeCombine(b: Option?): Option = - b?.let { combine(MA, it) } ?: this + b?.let { combine(MA as Semigroup, it) as Option } ?: this override fun empty(): Option = None } diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Semigroup.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Semigroup.kt index fd28e4ee3d3..02896562e1e 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Semigroup.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Semigroup.kt @@ -103,10 +103,10 @@ public fun interface Semigroup { ) : Semigroup> { override fun append(a: Option, b: Option): Option = - a.combine(SGA, b) + a.combine(SGA as Semigroup, b) as Option override fun Option.maybeCombine(b: Option?): Option = - b?.let { combine(SGA, it) } ?: this + b?.let { combine(SGA as Semigroup, it) as Option } ?: this } private class MapSemigroup(private val SG: Semigroup) : Semigroup> { diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/EitherTest.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/EitherTest.kt index 9f899b7a6d9..ca826cb68c1 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/EitherTest.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/EitherTest.kt @@ -184,7 +184,7 @@ class EitherTest : StringSpec({ "orNone should return None when left" { checkAll(Arb.string()) { a: String -> - Left(a).orNone() shouldBe None + Left(a).orNone() shouldBe None } } diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/OptionTest.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/OptionTest.kt index 126b56afb29..cea651e4b34 100755 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/OptionTest.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/OptionTest.kt @@ -44,7 +44,7 @@ class OptionTest : StringSpec({ } "short circuit null" { - option { + option { val number: Int = "s".length ensureNotNull(number.takeIf { it > 1 }) throw IllegalStateException("This should not be executed") @@ -55,10 +55,7 @@ class OptionTest : StringSpec({ checkAll(Arb.option(Arb.long())) { option -> var effect = 0 val res = option.tap { effect += 1 } - val expected = when (option) { - is Some -> 1 - is None -> 0 - } + val expected = option.fold({ 0 }, { 1 }) effect shouldBe expected res shouldBe option } @@ -68,10 +65,7 @@ class OptionTest : StringSpec({ checkAll(Arb.option(Arb.long())) { option -> var effect = 0 val res = option.tapNone { effect += 1 } - val expected = when (option) { - is Some -> 0 - is None -> 1 - } + val expected = option.fold({ 1 }, { 0 }) effect shouldBe expected res shouldBe option } @@ -140,7 +134,7 @@ class OptionTest : StringSpec({ none.align(some) { "$it" } shouldBe Some("Ior.Right(kotlin)") none.align(none) { "$it" } shouldBe None - val nullable = null.some() + val nullable: Option = null.some() some align nullable shouldBe Some(Ior.Both("kotlin", null)) nullable align some shouldBe Some(Ior.Both(null, "kotlin")) nullable align nullable shouldBe Some(Ior.Both(null, null)) @@ -168,7 +162,7 @@ class OptionTest : StringSpec({ someAny.filterIsInstance() shouldBe None val someNullableAny: Option = null.some() - someNullableAny.filterIsInstance() shouldBe Some(null) + someNullableAny.filterIsInstance() shouldBe Some(null) someNullableAny.filterIsInstance() shouldBe None val noneAny: Option = none @@ -208,10 +202,10 @@ class OptionTest : StringSpec({ emptyIterable.firstOrNone() shouldBe None val nullableIterable1 = iterableOf(null, 2, 3, 4, 5, 6) - nullableIterable1.firstOrNone() shouldBe Some(null) + nullableIterable1.firstOrNone() shouldBe Some(null) val nullableIterable2 = iterableOf(1, 2, 3, null, 5, null) - nullableIterable2.firstOrNone { it == null } shouldBe Some(null) + nullableIterable2.firstOrNone { it == null } shouldBe Some(null) } "Collection.firstOrNone" { @@ -222,7 +216,7 @@ class OptionTest : StringSpec({ emptyList.firstOrNone() shouldBe None val nullableList = listOf(null, 2, 3, 4, 5, 6) - nullableList.firstOrNone() shouldBe Some(null) + nullableList.firstOrNone() shouldBe Some(null) } "Iterable.singleOrNone" { @@ -235,10 +229,10 @@ class OptionTest : StringSpec({ singleIterable.singleOrNone { it == 3 } shouldBe Some(3) val nullableSingleIterable1 = iterableOf(null) - nullableSingleIterable1.singleOrNone() shouldBe Some(null) + nullableSingleIterable1.singleOrNone() shouldBe Some(null) val nullableSingleIterable2 = iterableOf(1, 2, 3, null, 5, 6) - nullableSingleIterable2.singleOrNone { it == null } shouldBe Some(null) + nullableSingleIterable2.singleOrNone { it == null } shouldBe Some(null) val nullableSingleIterable3 = iterableOf(1, 2, 3, null, 5, null) nullableSingleIterable3.singleOrNone { it == null } shouldBe None @@ -251,8 +245,8 @@ class OptionTest : StringSpec({ val singleList = listOf(3) singleList.singleOrNone() shouldBe Some(3) - val nullableSingleList = listOf(null) - nullableSingleList.singleOrNone() shouldBe Some(null) + val nullableSingleList: List = listOf(null) + nullableSingleList.singleOrNone() shouldBe Some(null) } "Iterable.lastOrNone" { @@ -265,10 +259,10 @@ class OptionTest : StringSpec({ emptyIterable.lastOrNone() shouldBe None val nullableIterable1 = iterableOf(1, 2, 3, 4, 5, null) - nullableIterable1.lastOrNone() shouldBe Some(null) + nullableIterable1.lastOrNone() shouldBe Some(null) val nullableIterable2 = iterableOf(null, 2, 3, null, 5, 6) - nullableIterable2.lastOrNone { it == null } shouldBe Some(null) + nullableIterable2.lastOrNone { it == null } shouldBe Some(null) } "Collection.lastOrNone" { @@ -279,7 +273,7 @@ class OptionTest : StringSpec({ emptyList.lastOrNone() shouldBe None val nullableList = listOf(1, 2, 3, 4, 5, null) - nullableList.lastOrNone() shouldBe Some(null) + nullableList.lastOrNone() shouldBe Some(null) } "Iterable.elementAtOrNone" { @@ -289,7 +283,7 @@ class OptionTest : StringSpec({ iterable.elementAtOrNone(index = 100) shouldBe None val nullableIterable = iterableOf(1, 2, null, 4, 5, 6) - nullableIterable.elementAtOrNone(index = 3 - 1) shouldBe Some(null) + nullableIterable.elementAtOrNone(index = 3 - 1) shouldBe Some(null) } "Collection.elementAtOrNone" { @@ -299,7 +293,7 @@ class OptionTest : StringSpec({ list.elementAtOrNone(index = 100) shouldBe None val nullableList = listOf(1, 2, null, 4, 5, 6) - nullableList.elementAtOrNone(index = 3 - 1) shouldBe Some(null) + nullableList.elementAtOrNone(index = 3 - 1) shouldBe Some(null) } "and" { @@ -365,7 +359,7 @@ class OptionTest : StringSpec({ "catch should return None when f throws" { val exception = Exception("Boom!") - Option.catch { throw exception } shouldBe None + Option.catch { throw exception } shouldBe None } }) diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/NullableSpec.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/NullableSpec.kt index 4cf6517484f..1115a065c51 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/NullableSpec.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/NullableSpec.kt @@ -2,6 +2,8 @@ package arrow.core.raise import arrow.core.Either import arrow.core.Some +import arrow.core.filter +import arrow.core.map import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe import io.kotest.property.Arb diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/OptionSpec.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/OptionSpec.kt index 2afc31f8544..d11c92ff382 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/OptionSpec.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/OptionSpec.kt @@ -1,6 +1,7 @@ package arrow.core.raise import arrow.core.None +import arrow.core.map import arrow.core.toOption import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe @@ -21,7 +22,7 @@ class OptionSpec : StringSpec({ } "short circuit option" { - option { + option { val number: Int = "s".length ensureNotNull(number.takeIf { it > 1 }) throw IllegalStateException("This should not be executed") @@ -39,7 +40,7 @@ class OptionSpec : StringSpec({ } "eager short circuit null" { - option { + option { val number: Int = "s".length ensureNotNull(number.takeIf { it > 1 }) throw IllegalStateException("This should not be executed") diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/test/Generators.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/test/Generators.kt index 9847a8faf2a..47f9cdc4519 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/test/Generators.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/test/Generators.kt @@ -61,7 +61,7 @@ fun Arb.Companion.longSmall(): Arb = fun Arb.Companion.endo(arb: Arb): Arb> = arb.map { a: A -> Endo { a } } -fun Arb.Companion.option(arb: Arb): Arb> = +inline fun Arb.Companion.option(arb: Arb): Arb> = arb.orNull().map { it.toOption() } fun Arb.Companion.either(arbE: Arb, arbA: Arb): Arb> { diff --git a/arrow-libs/core/arrow-core/src/jvmTest/java/arrow/core/DeadlockTest.kt b/arrow-libs/core/arrow-core/src/jvmTest/java/arrow/core/DeadlockTest.kt index 47450994cd8..71fa2153e9a 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/java/arrow/core/DeadlockTest.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/java/arrow/core/DeadlockTest.kt @@ -29,7 +29,7 @@ class DeadlockTest : StringSpec({ if (i % 2 == 0) { None } else { - Some(null) + Some(null) } } }.joinAll() diff --git a/arrow-libs/core/arrow-core/src/jvmTest/java/arrow/core/OptionUsage.java b/arrow-libs/core/arrow-core/src/jvmTest/java/arrow/core/OptionUsage.java deleted file mode 100644 index 23eb4be20b0..00000000000 --- a/arrow-libs/core/arrow-core/src/jvmTest/java/arrow/core/OptionUsage.java +++ /dev/null @@ -1,17 +0,0 @@ -package arrow.core; - -import kotlin.jvm.functions.Function1; - -public class OptionUsage { - - public void testUsage() { - Option fromNullable = Option.fromNullable(null); - Option.tryCatch((throwable) -> { - throwable.printStackTrace(); - return None.INSTANCE; - }, () -> 1); - - Option invoke = Option.invoke(1); - Function1, Option> lift = Option.lift((a) -> a.toUpperCase()); - } -} diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-41.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-41.kt index 9e1833e18ca..e2a7e1edb21 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-41.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-41.kt @@ -1,12 +1,10 @@ // This file was automatically generated from Either.kt by Knit tool. Do not edit. package arrow.core.examples.exampleEither41 -import arrow.core.Either -import arrow.core.Some -import arrow.core.None -import io.kotest.matchers.shouldBe +import arrow.core.* -fun test() { - Either.Right(12).getOrNone() shouldBe Some(12) - Either.Left(12).getOrNone() shouldBe None + fun main(args: Array) { + //sampleStart + Either.Left("foo").isEmpty() // Result: true + Either.Right("foo").isEmpty() // Result: false } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-42.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-42.kt index cd9256c06b2..7b32368d617 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-42.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-42.kt @@ -1,10 +1,11 @@ // This file was automatically generated from Either.kt by Knit tool. Do not edit. package arrow.core.examples.exampleEither42 -import arrow.core.* + import arrow.core.* fun main(args: Array) { //sampleStart - Either.Left("foo").isEmpty() // Result: true - Either.Right("foo").isEmpty() // Result: false + Either.Left("foo").isNotEmpty() // Result: false + Either.Right("foo").isNotEmpty() // Result: true + //sampleEnd } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-43.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-43.kt index e98eda5328a..07f14a4fd3e 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-43.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-43.kt @@ -1,11 +1,3 @@ // This file was automatically generated from Either.kt by Knit tool. Do not edit. package arrow.core.examples.exampleEither43 - import arrow.core.* - - fun main(args: Array) { - //sampleStart - Either.Left("foo").isNotEmpty() // Result: false - Either.Right("foo").isNotEmpty() // Result: true - //sampleEnd -} diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-44.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-44.kt index 27ed105719f..4a0f2763b33 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-44.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-either-44.kt @@ -1,3 +1,13 @@ // This file was automatically generated from Either.kt by Knit tool. Do not edit. package arrow.core.examples.exampleEither44 +import arrow.core.Either +import arrow.core.Some +import arrow.core.None +import arrow.core.getOrNone +import io.kotest.matchers.shouldBe + +fun test() { + Either.Right(12).getOrNone() shouldBe Some(12) + Either.Left(12).getOrNone() shouldBe None +} diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-01.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-01.kt index fb48ae937cf..4f275785966 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-01.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-01.kt @@ -1,9 +1,7 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption01 -import arrow.core.Option -import arrow.core.Some -import arrow.core.none +import arrow.core.* val someValue: Option = Some("I am wrapped in something") val emptyValue: Option = none() diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-02.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-02.kt index 1432d694e9f..8335b44adad 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-02.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-02.kt @@ -1,9 +1,7 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption02 -import arrow.core.None -import arrow.core.Option -import arrow.core.Some +import arrow.core.* fun maybeItWillReturnSomething(flag: Boolean): Option = if (flag) Some("Found value") else None diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-03.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-03.kt index 6da22ce3726..65344b0f8f0 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-03.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-03.kt @@ -1,10 +1,7 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption03 -import arrow.core.None -import arrow.core.Option -import arrow.core.Some -import arrow.core.getOrElse +import arrow.core.* fun maybeItWillReturnSomething(flag: Boolean): Option = if (flag) Some("Found value") else None diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-04.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-04.kt index 7c12966a132..cfa425a4f91 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-04.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-04.kt @@ -1,10 +1,7 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption04 -import arrow.core.None -import arrow.core.Option -import arrow.core.Some -import arrow.core.getOrElse +import arrow.core.* fun maybeItWillReturnSomething(flag: Boolean): Option = if (flag) Some("Found value") else None diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-05.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-05.kt index a361ab589a3..4e19ef0e829 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-05.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-05.kt @@ -1,16 +1,14 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption05 -import arrow.core.None -import arrow.core.Option -import arrow.core.Some +import arrow.core.* fun maybeItWillReturnSomething(flag: Boolean): Option = if (flag) Some("Found value") else None //sampleStart -val valueSome = maybeItWillReturnSomething(true) is None -val valueNone = maybeItWillReturnSomething(false) is None +val valueSome = maybeItWillReturnSomething(true).isEmpty() +val valueNone = maybeItWillReturnSomething(false).isEmpty() fun main() { println("valueSome = $valueSome") println("valueNone = $valueNone") diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-06.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-06.kt index 58aabd86f00..8b6a19cde5d 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-06.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-06.kt @@ -1,7 +1,7 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption06 -import arrow.core.Option +import arrow.core.* val myString: String? = "Nullable string" val option: Option = Option.fromNullable(myString) diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-07.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-07.kt index e1db8c17a18..6fd66e84e5a 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-07.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-07.kt @@ -1,15 +1,13 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption07 -import arrow.core.None -import arrow.core.Option -import arrow.core.Some +import arrow.core.* val someValue: Option = Some(20.0) -val value = when(someValue) { - is Some -> someValue.value - is None -> 0.0 -} +val value = someValue.fold( + { 0.0 }, + { it } +) fun main () { println("value = $value") } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-08.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-08.kt index 724583e8baa..4b935233ee1 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-08.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-08.kt @@ -1,15 +1,13 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption08 -import arrow.core.None -import arrow.core.Option -import arrow.core.Some +import arrow.core.* val noValue: Option = None -val value = when(noValue) { - is Some -> noValue.value - is None -> 0.0 -} +val value = noValue.fold( + { 0.0 }, + { it } +) fun main () { println("value = $value") } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-09.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-09.kt index d7a653b6594..9e39da4f853 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-09.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-09.kt @@ -1,9 +1,7 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption09 -import arrow.core.None -import arrow.core.Option -import arrow.core.Some +import arrow.core.* val number: Option = Some(3) val noNumber: Option = None diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-10.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-10.kt index d40b355b33f..98b556638ee 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-10.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-10.kt @@ -1,8 +1,7 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption10 -import arrow.core.Option -import arrow.core.Some +import arrow.core.* val fold = Some(3).fold({ 1 }, { it * 3 }) diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-11.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-11.kt index 9d6fb8aabf3..3c08f2097f7 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-11.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-11.kt @@ -3,6 +3,7 @@ package arrow.core.examples.exampleOption11 import arrow.core.Option import arrow.core.none +import arrow.core.fold val fold = none().fold({ 1 }, { it * 3 }) diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-12.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-12.kt index 52ce79b7e47..e3c78b2f8d5 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-12.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-12.kt @@ -1,8 +1,7 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption12 -import arrow.core.some -import arrow.core.none +import arrow.core.* val some = 1.some() val none = none() diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-13.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-13.kt index b7a2ebd9291..d397e69fa66 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-13.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-13.kt @@ -1,7 +1,7 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption13 -import arrow.core.toOption +import arrow.core.* val nullString: String? = null val valueFromNull = nullString.toOption() diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-14.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-14.kt index 589ff874dd1..a1d7c9b9e06 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-14.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-14.kt @@ -1,9 +1,7 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption14 -import arrow.core.firstOrNone -import arrow.core.toOption -import arrow.core.Option +import arrow.core.* val foxMap = mapOf(1 to "The", 2 to "Quick", 3 to "Brown", 4 to "Fox") diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-15.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-15.kt index 26350e3c3cd..ee0043bcdd9 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-15.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-15.kt @@ -1,7 +1,7 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption15 -import arrow.core.Some +import arrow.core.* fun main() { val value = diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-16.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-16.kt index f4cc52a3a80..41a4a01a7b6 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-16.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-16.kt @@ -1,7 +1,7 @@ // This file was automatically generated from Option.kt by Knit tool. Do not edit. package arrow.core.examples.exampleOption16 -import arrow.core.Some +import arrow.core.* val value = Some(1).zip(Some("Hello"), Some(20.0), ::Triple) diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-17.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-17.kt index 24a65d7ac6f..769055afa0b 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-17.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-17.kt @@ -3,6 +3,7 @@ package arrow.core.examples.exampleOption17 import arrow.core.Some import arrow.core.none +import arrow.core.tapNone fun main() { Some(12).tapNone { println("flower") } // Result: Some(12) diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-18.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-18.kt index d03af3c4b3a..0a265a97c92 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-18.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-18.kt @@ -3,6 +3,7 @@ package arrow.core.examples.exampleOption18 import arrow.core.Some import arrow.core.none +import arrow.core.tap fun main() { Some(12).tap { println("flower") } // Result: prints "flower" and returns: Some(12) diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-19.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-19.kt index de3db082548..9118d01b754 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-19.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-19.kt @@ -4,6 +4,7 @@ package arrow.core.examples.exampleOption19 import arrow.core.Some import arrow.core.None import arrow.core.Option +import arrow.core.exists fun main() { Some(12).exists { it > 10 } // Result: true diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-20.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-20.kt index 8c562e7f4a5..95433aaeaf7 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-20.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-option-20.kt @@ -4,6 +4,7 @@ package arrow.core.examples.exampleOption20 import arrow.core.Some import arrow.core.None import arrow.core.Option +import arrow.core.exists fun main() { Some(12).exists { it > 10 } // Result: 12 diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-raise-03.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-raise-03.kt index d3d3f2b9b26..0d69e120e5c 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-raise-03.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-raise-03.kt @@ -12,5 +12,5 @@ import arrow.core.identity suspend fun Effect.toEither(): Either = fold({ Either.Left(it) }) { Either.Right(it) } -suspend fun Effect.toOption(): Option = +suspend inline fun Effect, A>.toOption(): Option = fold(::identity) { Some(it) } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-raise-dsl-06.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-raise-dsl-06.kt index 91b3169be00..04b4303b9c5 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-raise-dsl-06.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-raise-dsl-06.kt @@ -5,6 +5,7 @@ import arrow.core.Either import arrow.core.None import arrow.core.Option import arrow.core.recover +import arrow.core.raise.bind import arrow.core.raise.either import kotlinx.coroutines.delay import io.kotest.matchers.shouldBe @@ -12,9 +13,9 @@ import io.kotest.matchers.shouldBe suspend fun test() { val empty: Option = None either { - val x: Int = empty.bind { _: None -> 1 } - val y: Int = empty.bind { _: None -> raise("Something bad happened: Boom!") } - val z: Int = empty.recover { _: None -> + val x: Int = empty.bind { _: Option -> 1 } + val y: Int = empty.bind { _: Option -> raise("Something bad happened: Boom!") } + val z: Int = empty.recover { _: Option -> delay(10) 1 }.bind { raise("Something bad happened: Boom!") } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/test/EitherKnitTest.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/test/EitherKnitTest.kt index a9f7f3b56f7..80bb830d839 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/test/EitherKnitTest.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/test/EitherKnitTest.kt @@ -28,8 +28,8 @@ class EitherKnitTest : StringSpec({ arrow.core.examples.exampleEither40.test() } - "ExampleEither41" { - arrow.core.examples.exampleEither41.test() + "ExampleEither44" { + arrow.core.examples.exampleEither44.test() } "ExampleEither46" { diff --git a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Optional.kt b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Optional.kt index 48786989670..6e7b3270e79 100644 --- a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Optional.kt +++ b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Optional.kt @@ -1,13 +1,6 @@ package arrow.optics -import arrow.core.Either -import arrow.core.None -import arrow.core.Option -import arrow.core.Some -import arrow.core.flatMap -import arrow.core.identity -import arrow.core.prependTo -import arrow.core.toOption +import arrow.core.* import arrow.typeclasses.Monoid import kotlin.jvm.JvmStatic diff --git a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Prism.kt b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Prism.kt index 92c0daa700a..6a2aab9990b 100644 --- a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Prism.kt +++ b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Prism.kt @@ -1,16 +1,8 @@ package arrow.optics -import arrow.core.Either +import arrow.core.* import arrow.core.Either.Left import arrow.core.Either.Right -import arrow.core.None -import arrow.core.Option -import arrow.core.Some -import arrow.core.compose -import arrow.core.flatMap -import arrow.core.identity -import arrow.core.left -import arrow.core.right import arrow.typeclasses.Monoid import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic diff --git a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/typeclasses/At.kt b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/typeclasses/At.kt index 9f96d1648b7..cfcfd68e2b4 100644 --- a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/typeclasses/At.kt +++ b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/typeclasses/At.kt @@ -2,6 +2,7 @@ package arrow.optics.typeclasses import arrow.core.None import arrow.core.Option +import arrow.core.fold import arrow.optics.Lens import arrow.optics.Optional import arrow.optics.PLens @@ -55,7 +56,7 @@ public fun interface At { public companion object { @JvmStatic - public fun map(): At, K, Option> = + public inline fun map(): At, K, Option> = At { i -> PLens( get = { Option.fromNullable(it[i]) }, diff --git a/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/OptionalTest.kt b/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/OptionalTest.kt index 6496857dc04..4288ea1d89e 100644 --- a/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/OptionalTest.kt +++ b/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/OptionalTest.kt @@ -2,8 +2,11 @@ package arrow.optics import arrow.core.Either.Left import arrow.core.Either.Right +import arrow.core.map +import arrow.core.fold import arrow.core.getOrElse import arrow.core.identity +import arrow.core.toList import arrow.core.toOption import arrow.optics.test.functionAToB import arrow.optics.test.laws.OptionalLaws From 069fd41ceae0886bc2982b9429a1dd48496fc2d3 Mon Sep 17 00:00:00 2001 From: Youssef Shoaib Date: Fri, 24 Feb 2023 08:27:31 +0000 Subject: [PATCH 2/6] Fix callable reference ambiguity --- .../arrow-core/src/commonMain/kotlin/arrow/core/Iterable.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Iterable.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Iterable.kt index ce0045cb808..f1bb3356c0d 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Iterable.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Iterable.kt @@ -1049,6 +1049,6 @@ public infix fun T.prependTo(list: Iterable): List = listOf(this) + list public inline fun Iterable>.filterOption(): List = - flatMap { it.fold(::emptyList, ::listOf) } + flatMap { it.fold(::emptyList) { listOf(it) } } public inline fun Iterable>.flattenOption(): List = filterOption() From 2f44a33d660d4b720a36c0c574e6fddedde0b5b5 Mon Sep 17 00:00:00 2001 From: Youssef Shoaib Date: Fri, 24 Feb 2023 09:30:50 +0000 Subject: [PATCH 3/6] Fix optics usage of Option --- .../src/commonMain/kotlin/arrow/optics/Every.kt | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Every.kt b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Every.kt index 9fb1faabffb..18e3dde6573 100644 --- a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Every.kt +++ b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Every.kt @@ -1,16 +1,6 @@ package arrow.optics -import arrow.core.Either -import arrow.core.NonEmptyList -import arrow.core.Option -import arrow.core.Tuple4 -import arrow.core.Tuple5 -import arrow.core.Tuple6 -import arrow.core.Tuple7 -import arrow.core.Tuple8 -import arrow.core.Tuple9 -import arrow.core.foldLeft -import arrow.core.foldMap +import arrow.core.* import arrow.typeclasses.Monoid import kotlin.jvm.JvmStatic @@ -84,7 +74,7 @@ public object Every { * @return [Traversal] with source [Option] and focus in every [arrow.core.Some] of the source. */ @JvmStatic - public fun option(): Traversal, A> = + public inline fun option(): Traversal, A> = object : Traversal, A> { override fun modify(source: Option, map: (focus: A) -> A): Option = source.map(map) From 7965ba2b4debddfc8944c2edd9e5a986653d03ee Mon Sep 17 00:00:00 2001 From: Youssef Shoaib Date: Fri, 24 Feb 2023 09:30:50 +0000 Subject: [PATCH 4/6] Fix optics usage of Option --- .../src/commonMain/kotlin/arrow/optics/Optional.kt | 8 ++++---- .../src/commonMain/kotlin/arrow/optics/Prism.kt | 8 ++++---- .../src/commonMain/kotlin/arrow/optics/Traversal.kt | 2 +- .../src/commonMain/kotlin/arrow/optics/dsl/nullable.kt | 4 ++-- .../src/commonMain/kotlin/arrow/optics/dsl/option.kt | 4 ++-- .../src/commonTest/kotlin/arrow/optics/OptionalTest.kt | 7 +------ .../src/commonTest/kotlin/arrow/optics/test/Generators.kt | 2 +- 7 files changed, 15 insertions(+), 20 deletions(-) diff --git a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Optional.kt b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Optional.kt index 6e7b3270e79..c29ed90191f 100644 --- a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Optional.kt +++ b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Optional.kt @@ -10,7 +10,7 @@ import kotlin.jvm.JvmStatic */ public typealias Optional = POptional -public fun Optional(getOption: (source: S) -> Option, set: (source: S, focus: A) -> S): Optional = +public inline fun Optional(crossinline getOption: (source: S) -> Option, noinline set: (source: S, focus: A) -> S): Optional = POptional({ s -> getOption(s).toEither { s } }, set) /** @@ -184,7 +184,7 @@ public interface POptional : PTraversal { * [Optional] to safely operate on the head of a list */ @JvmStatic - public fun listHead(): Optional, A> = Optional( + public inline fun listHead(): Optional, A> = Optional( getOption = { if (it.isNotEmpty()) Some(it[0]) else None }, set = { list, newHead -> if (list.isNotEmpty()) newHead prependTo list.drop(1) else emptyList() } ) @@ -202,7 +202,7 @@ public interface POptional : PTraversal { * [Optional] to safely operate in a nullable value. */ @JvmStatic - public fun nullable(): Optional = Optional( + public inline fun nullable(): Optional = Optional( getOption = { it.toOption() }, set = { source, new -> source?.let { new } } ) @@ -211,6 +211,6 @@ public interface POptional : PTraversal { * [Optional] to safely operate in a nullable value. */ @JvmStatic - public fun notNull(): Optional = nullable() + public inline fun notNull(): Optional = nullable() } } diff --git a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Prism.kt b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Prism.kt index 6a2aab9990b..4397dab8805 100644 --- a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Prism.kt +++ b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Prism.kt @@ -150,7 +150,7 @@ public interface PPrism : POptional { * [PPrism] to focus into an [arrow.core.Some] */ @JvmStatic - public fun pSome(): PPrism, Option, A, B> = + public inline fun pSome(): PPrism, Option, A, B> = PPrism( getOrModify = { option -> option.fold({ Left(None) }, ::Right) }, reverseGet = ::Some @@ -160,14 +160,14 @@ public interface PPrism : POptional { * [Prism] to focus into an [arrow.core.Some] */ @JvmStatic - public fun some(): Prism, A> = + public inline fun some(): Prism, A> = pSome() /** * [Prism] to focus into an [arrow.core.None] */ @JvmStatic - public fun none(): Prism, Unit> = + public inline fun none(): Prism, Unit> = Prism( getOrModify = { option -> option.fold({ Right(Unit) }, { Left(option) }) }, reverseGet = { _ -> None } @@ -221,7 +221,7 @@ public interface PPrism : POptional { * Invoke operator overload to create a [PPrism] of type `S` with a focus `A` where `A` is a subtype of `S` * Can also be used to construct [Prism] */ -public fun Prism(getOption: (source: S) -> Option, reverseGet: (focus: A) -> S): Prism = Prism( +public inline fun Prism(crossinline getOption: (source: S) -> Option, crossinline reverseGet: (focus: A) -> S): Prism = Prism( getOrModify = { getOption(it).toEither { it } }, reverseGet = { reverseGet(it) } ) diff --git a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Traversal.kt b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Traversal.kt index 5d06c9c42a4..ca0ad4285ad 100644 --- a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Traversal.kt +++ b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/Traversal.kt @@ -398,7 +398,7 @@ public interface PTraversal { * @return [Traversal] with source [Option] and focus in every [arrow.core.Some] of the source. */ @JvmStatic - public fun option(): Traversal, A> = + public inline fun option(): Traversal, A> = Every.option() @JvmStatic diff --git a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/dsl/nullable.kt b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/dsl/nullable.kt index af5d3c1df6b..3542ee4f3e7 100644 --- a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/dsl/nullable.kt +++ b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/dsl/nullable.kt @@ -12,7 +12,7 @@ import arrow.optics.Traversal * @receiver [Optional], [Lens], or [Prism] with a focus in <[S]?> * @return [Optional] with a focus in [S] */ -public inline val Optional.notNull: Optional inline get() = this.compose(POptional.notNull()) +public inline val Optional.notNull: Optional inline get() = this.compose(POptional.notNull()) /** * DSL to compose a [Traversal] with focus on a nullable type with [notNull]. @@ -20,4 +20,4 @@ public inline val Optional.notNull: Optional inline get() = * @receiver [Traversal] with a focus in <[S]?> * @return [Traversal] with a focus in [S] */ -public inline val Traversal.notNull: Traversal inline get() = this.compose(POptional.notNull()) +public inline val Traversal.notNull: Traversal inline get() = this.compose(POptional.notNull()) diff --git a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/dsl/option.kt b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/dsl/option.kt index 18239669eae..e954ad2b53f 100644 --- a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/dsl/option.kt +++ b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/dsl/option.kt @@ -12,7 +12,7 @@ import arrow.optics.Traversal * @receiver [Optional], [Lens], or [Prism] with a focus in [Option]<[S]> * @return [Optional] with a focus in [S] */ -public inline val Optional>.some: Optional inline get() = this.compose(Prism.some()) +public inline val Optional>.some: Optional inline get() = this.compose(Prism.some()) /** * DSL to compose a [Prism] with focus [arrow.core.Some] with a [Traversal] with a focus of [Option]<[S]> @@ -20,4 +20,4 @@ public inline val Optional>.some: Optional inline get( * @receiver [Traversal] with a focus in [Option]<[S]> * @return [Traversal] with a focus in [S] */ -public inline val Traversal>.some: Traversal inline get() = this.compose(Prism.some()) +public inline val Traversal>.some: Traversal inline get() = this.compose(Prism.some()) diff --git a/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/OptionalTest.kt b/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/OptionalTest.kt index 4288ea1d89e..19c555ccb71 100644 --- a/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/OptionalTest.kt +++ b/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/OptionalTest.kt @@ -1,13 +1,8 @@ package arrow.optics +import arrow.core.* import arrow.core.Either.Left import arrow.core.Either.Right -import arrow.core.map -import arrow.core.fold -import arrow.core.getOrElse -import arrow.core.identity -import arrow.core.toList -import arrow.core.toOption import arrow.optics.test.functionAToB import arrow.optics.test.laws.OptionalLaws import arrow.optics.test.laws.testLaws diff --git a/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/test/Generators.kt b/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/test/Generators.kt index 0d3ddd5ed4e..06663c49924 100644 --- a/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/test/Generators.kt +++ b/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/test/Generators.kt @@ -28,7 +28,7 @@ fun Arb.Companion.sequence(arb: Arb, range: IntRange = 0 .. 100): Arb Arb.Companion.functionAToB(arb: Arb): Arb<(A) -> B> = arb.map { b: B -> { _: A -> b } } -fun Arb.Companion.option(arb: Arb): Arb> = +inline fun Arb.Companion.option(arb: Arb): Arb> = arb.orNull().map { it.toOption() } fun Arb.Companion.either(arbE: Arb, arbA: Arb): Arb> { From 1bf327e6d619a8c46262e5f4a2689d9bf09ebf8c Mon Sep 17 00:00:00 2001 From: Youssef Shoaib Date: Fri, 24 Feb 2023 09:30:50 +0000 Subject: [PATCH 5/6] Fix optics usage of Option --- .../src/main/kotlin/arrow/optics/Reflection.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arrow-libs/optics/arrow-optics-reflect/src/main/kotlin/arrow/optics/Reflection.kt b/arrow-libs/optics/arrow-optics-reflect/src/main/kotlin/arrow/optics/Reflection.kt index bbe8606c584..b0203a4e0a3 100644 --- a/arrow-libs/optics/arrow-optics-reflect/src/main/kotlin/arrow/optics/Reflection.kt +++ b/arrow-libs/optics/arrow-optics-reflect/src/main/kotlin/arrow/optics/Reflection.kt @@ -36,7 +36,7 @@ public val KProperty1.lens: Lens ) /** [Optional] that focuses on a nullable field */ -public val KProperty1.optional: Optional +public inline val KProperty1.optional: Optional get() = lens compose Optional.nullable() public val KProperty1>.every: Traversal From b6cd5356dd48c5d56d7a42b6f1d594943932c147 Mon Sep 17 00:00:00 2001 From: Youssef Shoaib Date: Sun, 26 Feb 2023 21:33:51 +0000 Subject: [PATCH 6/6] Reformat and fix Semigroup CCE --- .../commonMain/kotlin/arrow/core/Option.kt | 65 ++++++++++++++++++- .../kotlin/arrow/core/raise/Builders.kt | 1 - .../kotlin/arrow/typeclasses/Monoid.kt | 3 +- .../kotlin/arrow/typeclasses/Semigroup.kt | 3 +- .../kotlin/arrow/core/OptionTest.kt | 12 ++++ 5 files changed, 79 insertions(+), 5 deletions(-) diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Option.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Option.kt index bdb83b03b96..f23a454efe3 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Option.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Option.kt @@ -1,12 +1,13 @@ @file:OptIn(ExperimentalContracts::class) - package arrow.core import arrow.core.Option.Companion.fromNullable import arrow.core.raise.OptionRaise import arrow.core.raise.option import arrow.typeclasses.Monoid +import arrow.typeclasses.Monoid.Companion.OptionMonoid import arrow.typeclasses.Semigroup +import arrow.typeclasses.Semigroup.Companion.OptionSemigroup import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -995,6 +996,18 @@ public inline fun Option.getOrElse(default: () -> T): T { return fold({ default() }, ::identity) } +/** + * Returns the option's value if the option is nonempty, otherwise + * return the result of evaluating `default`. + * + * @param default the default expression. + */ +@JvmName("getOrElseOption") +public inline fun Option>.getOrElse(default: () -> Option): Option { + contract { callsInPlace(default, InvocationKind.AT_MOST_ONCE) } + return fold({ default() }, ::identity) +} + /** * Returns this option's if the option is nonempty, otherwise * returns another option provided lazily by `default`. @@ -1175,10 +1188,58 @@ public fun Option>.toMap(): Map = this.toList().toMap() public inline fun Option.combine(SGA: Semigroup, b: Option): Option = fold({ b }) { a -> b.fold({ this }) { b -> - Some(SGA.run { a.combine(b) }) + SGA.combineToOption(a, b) } } +// The purpose of this function is that, within the implementation of OptionSemigroup/Monoid +// We can't know whether A inside the Option is meant to be a Option>. The impl just casts the Semigroup to +// a Semigroup and lets nature take its course. If the values are meant to be a nested Option, then the impl would fail +// because it would pass the raw value of the nested Option instead of a boxed value. We deal with this here in the else branch +// by ensuring that we coerce our values to be an Option if $this is an OptionSemigroup/Monoid. Also, in the if branch +// we have some minor optimisations to ensure that first and second aren't boxed if they're intended to be passed to OS/OM +@OptIn(OptionInternals::class) +@Suppress("UNCHECKED_CAST") +@PublishedApi +internal inline fun Semigroup.combineToOption(first: A, second: A): Option = + if (isTypeOption() && null !is A) { + val first = first as Option<*> + val second = second as Option<*> + // It's important here to only cast the value as A when it's inside the option, otherwise we get unnecessary boxing + when { + this is OptionMonoid<*> -> + Some((this as OptionMonoid).append(first, second)) as Option + + this is OptionSemigroup<*> -> + Some((this as OptionSemigroup).append(first, second)) as Option + + else -> + Some((this as Semigroup>).append(first.rebox(), second.rebox()) as A) + } + } else { + when (this) { + is OptionMonoid<*> -> { + val first = if (first is Option<*>) first else Option.construct(first!!) + val second = if (second is Option<*>) second else Option.construct(second!!) + Some((this as OptionMonoid).append(first, second)) as Option + } + + is OptionSemigroup<*> -> { + val first = if (first is Option<*>) first else Option.construct(first!!) + val second = if (second is Option<*>) second else Option.construct(second!!) + Some((this as OptionSemigroup).append(first, second)) as Option + } + else -> Some(first.combine(second)) + } + } + +/** + * Purposefully NOT inline because, as the name suggests, this will cause the [this] to be reboxed, + * which convinces the compiler that [this] Maybe is not being used in an Any or generic context + */ +@PublishedApi +internal fun Option.rebox(): Option = this + public inline operator fun > Option.compareTo(other: Option): Int = fold( { other.fold({ 0 }, { -1 }) }, { a1 -> diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt index 0e0bf8dd9a5..ecfa896bedd 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt @@ -1,7 +1,6 @@ @file:JvmMultifileClass @file:JvmName("RaiseKt") @file:OptIn(ExperimentalTypeInference::class, ExperimentalContracts::class) - package arrow.core.raise import arrow.atomic.Atomic diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Monoid.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Monoid.kt index 499f7866c79..28e5ae82bf1 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Monoid.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Monoid.kt @@ -100,7 +100,8 @@ public interface Monoid : Semigroup { public fun pair(MA: Monoid, MB: Monoid): Monoid> = PairMonoid(MA, MB) - private class OptionMonoid( + @PublishedApi + internal class OptionMonoid( private val MA: Semigroup ) : Monoid> { diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Semigroup.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Semigroup.kt index 02896562e1e..60ae992863f 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Semigroup.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/typeclasses/Semigroup.kt @@ -98,7 +98,8 @@ public fun interface Semigroup { NonEmptyList(a.head, a.tail.plus(b)) } - private class OptionSemigroup( + @PublishedApi + internal class OptionSemigroup( private val SGA: Semigroup ) : Semigroup> { diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/OptionTest.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/OptionTest.kt index cea651e4b34..704ea53e96f 100755 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/OptionTest.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/OptionTest.kt @@ -5,6 +5,7 @@ import arrow.core.test.laws.MonoidLaws import arrow.core.test.option import arrow.core.test.testLaws import arrow.typeclasses.Monoid +import arrow.typeclasses.Semigroup import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe @@ -23,6 +24,17 @@ class OptionTest : StringSpec({ testLaws( MonoidLaws.laws(Monoid.option(Monoid.int()), Arb.option(Arb.int())), ) + testLaws( + MonoidLaws.laws(Monoid.option(Semigroup.option(Monoid.int())), Arb.option(Arb.option(Arb.int()))), + ) + + testLaws( + MonoidLaws.laws(Monoid.option(Semigroup.option(Monoid.option(Monoid.int()))), Arb.option(Arb.option(Arb.option(Arb.int())))), + ) + + testLaws( + MonoidLaws.laws(Monoid.option(Monoid.option(Semigroup.option(Monoid.int()))), Arb.option(Arb.option(Arb.option(Arb.int())))), + ) "ensure null in option computation" { checkAll(Arb.boolean(), Arb.int()) { predicate, i ->