diff --git a/arrow-libs/optics/arrow-match/api/arrow-match.api b/arrow-libs/optics/arrow-match/api/arrow-match.api new file mode 100644 index 0000000000..1cf7bdcc80 --- /dev/null +++ b/arrow-libs/optics/arrow-match/api/arrow-match.api @@ -0,0 +1,42 @@ +public final class arrow/match/BuildersKt { + public static final fun identity ()Lkotlin/reflect/KProperty1; + public static final fun isNotEmpty (Lkotlin/reflect/KProperty1;)Lkotlin/reflect/KProperty1; + public static final fun of (Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;)Lkotlin/reflect/KProperty1; + public static final fun of (Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;)Lkotlin/reflect/KProperty1; + public static final fun of (Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;)Lkotlin/reflect/KProperty1; + public static final fun of (Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;)Lkotlin/reflect/KProperty1; + public static final fun of (Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;Lkotlin/reflect/KProperty1;)Lkotlin/reflect/KProperty1; + public static final fun takeIf (Lkotlin/reflect/KProperty1;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lkotlin/reflect/KProperty1; + public static synthetic fun takeIf$default (Lkotlin/reflect/KProperty1;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlin/reflect/KProperty1; +} + +public final class arrow/match/DoesNotMatch : java/lang/Throwable { + public fun ()V + public fun fillInStackTrace ()Ljava/lang/Throwable; +} + +public final class arrow/match/MatchKt { + public static final fun matchOrElse (Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static final fun matchOrRaise (Larrow/core/raise/Raise;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static final fun matchOrRaise (Larrow/core/raise/SingletonRaise;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static final fun matchOrThrow (Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static synthetic fun matchOrThrow$default (Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun matchUnit (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V +} + +public final class arrow/match/MatchNotFound : java/lang/Throwable { + public fun (Ljava/lang/Object;)V + public final fun getValue ()Ljava/lang/Object; +} + +public abstract class arrow/match/MatchScope { + public fun ()V + public abstract fun default (Lkotlin/jvm/functions/Function0;)V + public final fun getIt ()Lkotlin/reflect/KProperty1; + public abstract fun then (Lkotlin/reflect/KProperty1;Lkotlin/jvm/functions/Function1;)V +} + +public final class arrow/match/MatcherKt { + public static final fun Matcher (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lkotlin/reflect/KProperty1; +} + diff --git a/arrow-libs/optics/arrow-match/api/arrow-match.klib.api b/arrow-libs/optics/arrow-match/api/arrow-match.klib.api new file mode 100644 index 0000000000..415e563818 --- /dev/null +++ b/arrow-libs/optics/arrow-match/api/arrow-match.klib.api @@ -0,0 +1,51 @@ +// Klib ABI Dump +// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +abstract class <#A: kotlin/Any?, #B: kotlin/Any?> arrow.match/MatchScope { // arrow.match/MatchScope|null[0] + constructor () // arrow.match/MatchScope.|(){}[0] + + final val it // arrow.match/MatchScope.it|{}it[0] + final fun (): kotlin.reflect/KProperty1<#A, #A> // arrow.match/MatchScope.it.|(){}[0] + + abstract fun <#A1: kotlin/Any?> (kotlin.reflect/KProperty1<#A, #A1>).then(kotlin/Function1<#A1, #B>) // arrow.match/MatchScope.then|then@kotlin.reflect.KProperty1<1:0,0:0>(kotlin.Function1<0:0,1:1>){0§}[0] + abstract fun default(kotlin/Function0<#B>) // arrow.match/MatchScope.default|default(kotlin.Function0<1:1>){}[0] + final inline fun <#A1: reified #A!!, #B1: kotlin/Any?, #C1: kotlin/Any?, #D1: kotlin/Any?, #E1: kotlin/Any?, #F1: kotlin/Any?> (kotlin.reflect/KClass<#A1>).of(kotlin.reflect/KProperty1<#A1, #B1>, kotlin.reflect/KProperty1<#A1, #C1>, kotlin.reflect/KProperty1<#A1, #D1>, kotlin.reflect/KProperty1<#A1, #E1>, kotlin.reflect/KProperty1<#A1, #F1>): kotlin.reflect/KProperty1<#A, arrow.core/Tuple5<#B1, #C1, #D1, #E1, #F1>> // arrow.match/MatchScope.of|of@kotlin.reflect.KClass<0:0>(kotlin.reflect.KProperty1<0:0,0:1>;kotlin.reflect.KProperty1<0:0,0:2>;kotlin.reflect.KProperty1<0:0,0:3>;kotlin.reflect.KProperty1<0:0,0:4>;kotlin.reflect.KProperty1<0:0,0:5>){0§<1:0>;1§;2§;3§;4§;5§}[0] + final inline fun <#A1: reified #A!!, #B1: kotlin/Any?, #C1: kotlin/Any?, #D1: kotlin/Any?, #E1: kotlin/Any?> (kotlin.reflect/KClass<#A1>).of(kotlin.reflect/KProperty1<#A1, #B1>, kotlin.reflect/KProperty1<#A1, #C1>, kotlin.reflect/KProperty1<#A1, #D1>, kotlin.reflect/KProperty1<#A1, #E1>): kotlin.reflect/KProperty1<#A, arrow.core/Tuple4<#B1, #C1, #D1, #E1>> // arrow.match/MatchScope.of|of@kotlin.reflect.KClass<0:0>(kotlin.reflect.KProperty1<0:0,0:1>;kotlin.reflect.KProperty1<0:0,0:2>;kotlin.reflect.KProperty1<0:0,0:3>;kotlin.reflect.KProperty1<0:0,0:4>){0§<1:0>;1§;2§;3§;4§}[0] + final inline fun <#A1: reified #A!!, #B1: kotlin/Any?, #C1: kotlin/Any?, #D1: kotlin/Any?> (kotlin.reflect/KClass<#A1>).of(kotlin.reflect/KProperty1<#A1, #B1>, kotlin.reflect/KProperty1<#A1, #C1>, kotlin.reflect/KProperty1<#A1, #D1>): kotlin.reflect/KProperty1<#A, kotlin/Triple<#B1, #C1, #D1>> // arrow.match/MatchScope.of|of@kotlin.reflect.KClass<0:0>(kotlin.reflect.KProperty1<0:0,0:1>;kotlin.reflect.KProperty1<0:0,0:2>;kotlin.reflect.KProperty1<0:0,0:3>){0§<1:0>;1§;2§;3§}[0] + final inline fun <#A1: reified #A!!, #B1: kotlin/Any?, #C1: kotlin/Any?> (kotlin.reflect/KClass<#A1>).of(kotlin.reflect/KProperty1<#A1, #B1>, kotlin.reflect/KProperty1<#A1, #C1>): kotlin.reflect/KProperty1<#A, kotlin/Pair<#B1, #C1>> // arrow.match/MatchScope.of|of@kotlin.reflect.KClass<0:0>(kotlin.reflect.KProperty1<0:0,0:1>;kotlin.reflect.KProperty1<0:0,0:2>){0§<1:0>;1§;2§}[0] + final inline fun <#A1: reified #A!!, #B1: kotlin/Any?> (kotlin.reflect/KClass<#A1>).of(kotlin.reflect/KProperty1<#A1, #B1>): kotlin.reflect/KProperty1<#A, #B1> // arrow.match/MatchScope.of|of@kotlin.reflect.KClass<0:0>(kotlin.reflect.KProperty1<0:0,0:1>){0§<1:0>;1§}[0] +} + +final class arrow.match/DoesNotMatch : kotlin/Throwable { // arrow.match/DoesNotMatch|null[0] + constructor () // arrow.match/DoesNotMatch.|(){}[0] +} + +final class arrow.match/MatchNotFound : kotlin/Throwable { // arrow.match/MatchNotFound|null[0] + constructor (kotlin/Any?) // arrow.match/MatchNotFound.|(kotlin.Any?){}[0] + + final val value // arrow.match/MatchNotFound.value|{}value[0] + final fun (): kotlin/Any? // arrow.match/MatchNotFound.value.|(){}[0] +} + +final val arrow.match/isNotEmpty // arrow.match/isNotEmpty|@kotlin.reflect.KProperty1<0:0,kotlin.collections.Collection<0:1>>{0§;1§}isNotEmpty[0] + final fun <#A1: kotlin/Any?, #B1: kotlin/Any?> (kotlin.reflect/KProperty1<#A1, kotlin.collections/Collection<#B1>>).(): kotlin.reflect/KProperty1<#A1, kotlin.collections/Collection<#B1>> // arrow.match/isNotEmpty.|@kotlin.reflect.KProperty1<0:0,kotlin.collections.Collection<0:1>>(){0§;1§}[0] + +final fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?, #D: kotlin/Any?, #E: kotlin/Any?, #F: kotlin/Any?, #G: kotlin/Any?> (kotlin.reflect/KProperty1<#A, #B>).arrow.match/of(kotlin.reflect/KProperty1<#B, #C>, kotlin.reflect/KProperty1<#B, #D>, kotlin.reflect/KProperty1<#B, #E>, kotlin.reflect/KProperty1<#B, #F>, kotlin.reflect/KProperty1<#B, #G>): kotlin.reflect/KProperty1<#A, arrow.core/Tuple5<#C, #D, #E, #F, #G>> // arrow.match/of|of@kotlin.reflect.KProperty1<0:0,0:1>(kotlin.reflect.KProperty1<0:1,0:2>;kotlin.reflect.KProperty1<0:1,0:3>;kotlin.reflect.KProperty1<0:1,0:4>;kotlin.reflect.KProperty1<0:1,0:5>;kotlin.reflect.KProperty1<0:1,0:6>){0§;1§;2§;3§;4§;5§;6§}[0] +final fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?, #D: kotlin/Any?, #E: kotlin/Any?, #F: kotlin/Any?> (kotlin.reflect/KProperty1<#A, #B>).arrow.match/of(kotlin.reflect/KProperty1<#B, #C>, kotlin.reflect/KProperty1<#B, #D>, kotlin.reflect/KProperty1<#B, #E>, kotlin.reflect/KProperty1<#B, #F>): kotlin.reflect/KProperty1<#A, arrow.core/Tuple4<#C, #D, #E, #F>> // arrow.match/of|of@kotlin.reflect.KProperty1<0:0,0:1>(kotlin.reflect.KProperty1<0:1,0:2>;kotlin.reflect.KProperty1<0:1,0:3>;kotlin.reflect.KProperty1<0:1,0:4>;kotlin.reflect.KProperty1<0:1,0:5>){0§;1§;2§;3§;4§;5§}[0] +final fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?, #D: kotlin/Any?, #E: kotlin/Any?> (kotlin.reflect/KProperty1<#A, #B>).arrow.match/of(kotlin.reflect/KProperty1<#B, #C>, kotlin.reflect/KProperty1<#B, #D>, kotlin.reflect/KProperty1<#B, #E>): kotlin.reflect/KProperty1<#A, kotlin/Triple<#C, #D, #E>> // arrow.match/of|of@kotlin.reflect.KProperty1<0:0,0:1>(kotlin.reflect.KProperty1<0:1,0:2>;kotlin.reflect.KProperty1<0:1,0:3>;kotlin.reflect.KProperty1<0:1,0:4>){0§;1§;2§;3§;4§}[0] +final fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?, #D: kotlin/Any?> (kotlin.reflect/KProperty1<#A, #B>).arrow.match/of(kotlin.reflect/KProperty1<#B, #C>, kotlin.reflect/KProperty1<#B, #D>): kotlin.reflect/KProperty1<#A, kotlin/Pair<#C, #D>> // arrow.match/of|of@kotlin.reflect.KProperty1<0:0,0:1>(kotlin.reflect.KProperty1<0:1,0:2>;kotlin.reflect.KProperty1<0:1,0:3>){0§;1§;2§;3§}[0] +final fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?> (arrow.core.raise/SingletonRaise<#C>).arrow.match/matchOrRaise(#A, kotlin/Function1, kotlin/Unit>): #B // arrow.match/matchOrRaise|matchOrRaise@arrow.core.raise.SingletonRaise<0:2>(0:0;kotlin.Function1,kotlin.Unit>){0§;1§;2§}[0] +final fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?> (kotlin.reflect/KProperty1<#A, #B>).arrow.match/of(kotlin.reflect/KProperty1<#B, #C>): kotlin.reflect/KProperty1<#A, #C> // arrow.match/of|of@kotlin.reflect.KProperty1<0:0,0:1>(kotlin.reflect.KProperty1<0:1,0:2>){0§;1§;2§}[0] +final fun <#A: kotlin/Any?, #B: kotlin/Any?> (#A).arrow.match/matchOrElse(kotlin/Function0<#B>, kotlin/Function1, kotlin/Unit>): #B // arrow.match/matchOrElse|matchOrElse@0:0(kotlin.Function0<0:1>;kotlin.Function1,kotlin.Unit>){0§;1§}[0] +final fun <#A: kotlin/Any?, #B: kotlin/Any?> (#A).arrow.match/matchOrThrow(kotlin/Function0 = ..., kotlin/Function1, kotlin/Unit>): #B // arrow.match/matchOrThrow|matchOrThrow@0:0(kotlin.Function0;kotlin.Function1,kotlin.Unit>){0§;1§}[0] +final fun <#A: kotlin/Any?, #B: kotlin/Any?> (arrow.core.raise/Raise).arrow.match/matchOrRaise(#A, kotlin/Function1, kotlin/Unit>): #B // arrow.match/matchOrRaise|matchOrRaise@arrow.core.raise.Raise(0:0;kotlin.Function1,kotlin.Unit>){0§;1§}[0] +final fun <#A: kotlin/Any?, #B: kotlin/Any?> (kotlin.reflect/KProperty1<#A, #B>).arrow.match/takeIf(kotlin/String? = ..., kotlin/Function1<#B, kotlin/Boolean>): kotlin.reflect/KProperty1<#A, #B> // arrow.match/takeIf|takeIf@kotlin.reflect.KProperty1<0:0,0:1>(kotlin.String?;kotlin.Function1<0:1,kotlin.Boolean>){0§;1§}[0] +final fun <#A: kotlin/Any?, #B: kotlin/Any?> arrow.match/Matcher(kotlin/String, kotlin/Function1<#A, #B>): kotlin.reflect/KProperty1<#A, #B> // arrow.match/Matcher|Matcher(kotlin.String;kotlin.Function1<0:0,0:1>){0§;1§}[0] +final fun <#A: kotlin/Any?> (#A).arrow.match/matchUnit(kotlin/Function1, kotlin/Unit>) // arrow.match/matchUnit|matchUnit@0:0(kotlin.Function1,kotlin.Unit>){0§}[0] +final fun <#A: kotlin/Any?> arrow.match/identity(): kotlin.reflect/KProperty1<#A, #A> // arrow.match/identity|identity(){0§}[0] +final inline fun <#A: kotlin/Any?, #B: reified #A> arrow.match/instanceOf(): kotlin.reflect/KProperty1<#A, #B> // arrow.match/instanceOf|instanceOf(){0§;1§<0:0>}[0] diff --git a/arrow-libs/optics/arrow-match/build.gradle.kts b/arrow-libs/optics/arrow-match/build.gradle.kts new file mode 100644 index 0000000000..0dd83eae51 --- /dev/null +++ b/arrow-libs/optics/arrow-match/build.gradle.kts @@ -0,0 +1,50 @@ +@file:Suppress("DSL_SCOPE_VIOLATION") + +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + + +plugins { + id(libs.plugins.kotlin.multiplatform.get().pluginId) + alias(libs.plugins.arrowGradleConfig.kotlin) + alias(libs.plugins.publish) + alias(libs.plugins.spotless) +} + +spotless { + kotlin { + ktlint().editorConfigOverride(mapOf("ktlint_standard_filename" to "disabled")) + } +} + +apply(from = property("ANIMALSNIFFER_MPP")) + +kotlin { + sourceSets { + commonMain { + dependencies { + implementation(libs.kotlin.stdlib) + implementation(projects.arrowCore) + } + } + commonTest { + dependencies { + implementation(libs.kotlin.test) + implementation(libs.kotest.assertionsCore) + implementation(libs.coroutines.test) + } + } + } + + jvm { + tasks.jvmJar { + manifest { + attributes["Automatic-Module-Name"] = "arrow.match" + } + } + } +} + +// enables context receivers for Jvm Tests +tasks.named("compileTestKotlinJvm") { + compilerOptions.freeCompilerArgs.add("-Xcontext-receivers") +} diff --git a/arrow-libs/optics/arrow-match/gradle.properties b/arrow-libs/optics/arrow-match/gradle.properties new file mode 100644 index 0000000000..211d8ab440 --- /dev/null +++ b/arrow-libs/optics/arrow-match/gradle.properties @@ -0,0 +1,2 @@ +# Maven publishing configuration +POM_NAME=Arrow Match diff --git a/arrow-libs/optics/arrow-match/src/commonMain/kotlin/arrow/match/Builders.kt b/arrow-libs/optics/arrow-match/src/commonMain/kotlin/arrow/match/Builders.kt new file mode 100644 index 0000000000..65cdb1bdbf --- /dev/null +++ b/arrow-libs/optics/arrow-match/src/commonMain/kotlin/arrow/match/Builders.kt @@ -0,0 +1,88 @@ +package arrow.match + +import arrow.core.Tuple4 +import arrow.core.Tuple5 +import kotlin.reflect.KProperty1 + +/** + * A matcher is a [KProperty1] which may throw [DoesNotMatch] + * to signal that it does not match the value. + */ +public typealias Matcher = KProperty1 + +public expect fun Matcher( + name: String, + get: (S) -> A +): Matcher + +public expect class DoesNotMatch(): Throwable + +public fun identity(): Matcher = Matcher("identity") { it } + +public inline fun instanceOf(): Matcher = + Matcher("instanceOf<${A::class.simpleName}>") { + if (it is A) it else throw DoesNotMatch() + } + +public fun Matcher.takeIf( + description: String? = null, + predicate: (A) -> Boolean +): Matcher = Matcher("${this.name}.${description ?: "suchThat"}") { + val value = this.get(it) + if (predicate(value)) value else throw DoesNotMatch() +} + +public val Matcher>.isNotEmpty: Matcher> + get() = this.takeIf("isNotEmpty") { it.isNotEmpty() } + +public fun Matcher.of( + field: Matcher +): Matcher = Matcher("${this.name}.of(${field.name})") { + field.get(this.get(it)) +} + +public fun Matcher.of( + field1: Matcher, + field2: Matcher +): Matcher> = Matcher( + "${this.name}.of(${field1.name}, ${field2.name})" +) { + val a = this.get(it) + Pair(field1.get(a), field2.get(a)) +} + +public fun Matcher.of( + field1: Matcher, + field2: Matcher, + field3: Matcher +): Matcher> = Matcher( + "${this.name}.of(${field1.name}, ${field2.name}, ${field3.name})" +) { + val a = this.get(it) + Triple(field1.get(a), field2.get(a), field3.get(a)) +} + +public fun Matcher.of( + field1: Matcher, + field2: Matcher, + field3: Matcher, + field4: Matcher +): Matcher> = Matcher( + "${this.name}.of(${field1.name}, ${field2.name}, ${field3.name}, ${field4.name})" +) { + val a = this.get(it) + Tuple4(field1.get(a), field2.get(a), field3.get(a), field4.get(a)) +} + +public fun Matcher.of( + field1: Matcher, + field2: Matcher, + field3: Matcher, + field4: Matcher, + field5: Matcher +): Matcher> = Matcher( + "${this.name}.of(${field1.name}, ${field2.name}, ${field3.name}, ${field4.name}, ${field5.name})" +) { + val a = this.get(it) + Tuple5(field1.get(a), field2.get(a), field3.get(a), field4.get(a), field5.get(a)) +} diff --git a/arrow-libs/optics/arrow-match/src/commonMain/kotlin/arrow/match/Match.kt b/arrow-libs/optics/arrow-match/src/commonMain/kotlin/arrow/match/Match.kt new file mode 100644 index 0000000000..48206bc44c --- /dev/null +++ b/arrow-libs/optics/arrow-match/src/commonMain/kotlin/arrow/match/Match.kt @@ -0,0 +1,91 @@ +package arrow.match + +import arrow.core.Tuple4 +import arrow.core.Tuple5 +import kotlin.reflect.KClass +import arrow.core.raise.Raise +import arrow.core.raise.SingletonRaise + +public abstract class MatchScope { + public abstract infix fun Matcher.then(next: (A) -> R) + public abstract fun default(next: () -> R) + + public val it: Matcher = Matcher("it") { it } + + public inline fun KClass.of( + field: Matcher + ): Matcher = instanceOf().of(field) + + public inline fun KClass.of( + field1: Matcher, + field2: Matcher + ): Matcher> = instanceOf().of(field1, field2) + + public inline fun KClass.of( + field1: Matcher, + field2: Matcher, + field3: Matcher + ): Matcher> = instanceOf().of(field1, field2, field3) + + public inline fun KClass.of( + field1: Matcher, + field2: Matcher, + field3: Matcher, + field4: Matcher + ): Matcher> = instanceOf().of(field1, field2, field3, field4) + + public inline fun KClass.of( + field1: Matcher, + field2: Matcher, + field3: Matcher, + field4: Matcher, + field5: Matcher + ): Matcher> = instanceOf().of(field1, field2, field3, field4, field5) +} + +private class MatchScopeImpl(val subject: S): MatchScope() { + override fun Matcher.then(next: (A) -> R) { + try { + this.get(subject)?.let { throw MatchFound(next(it)) } + } catch (e: DoesNotMatch) { + /* do nothing */ + } + } + override fun default(next: () -> R) { + throw MatchFound(next()) + } +} + +private class MatchFound(val result: Any?) : Throwable() + +@Suppress("UNCHECKED_CAST") +public fun S.matchOrElse( + noMatch: () -> R, + cases: MatchScope.() -> Unit, +): R = try { + cases(MatchScopeImpl(this)) + noMatch() +} catch (e: MatchFound) { + e.result as R +} + +public class MatchNotFound(public val value: Any?) : Throwable() + +public fun S.matchOrThrow( + exception: () -> Throwable = { MatchNotFound(this) }, + cases: MatchScope.() -> Unit, +): R = matchOrElse({ throw exception() }, cases) + +public fun Raise.matchOrRaise( + value: S, + cases: MatchScope.() -> Unit, +): R = value.matchOrElse({ raise(MatchNotFound(value)) }, cases) + +public fun SingletonRaise.matchOrRaise( + value: S, + cases: MatchScope.() -> Unit, +): R = value.matchOrElse({ raise() }, cases) + +public fun S.matchUnit( + cases: MatchScope.() -> Unit, +): Unit = matchOrElse({ }, cases) diff --git a/arrow-libs/optics/arrow-match/src/jsMain/kotlin/arrow/match/Matcher.kt b/arrow-libs/optics/arrow-match/src/jsMain/kotlin/arrow/match/Matcher.kt new file mode 100644 index 0000000000..a54a1c3c22 --- /dev/null +++ b/arrow-libs/optics/arrow-match/src/jsMain/kotlin/arrow/match/Matcher.kt @@ -0,0 +1,11 @@ +package arrow.match + +@Suppress("IMPLEMENTING_FUNCTION_INTERFACE") +public actual fun Matcher( + name: String, + get: (S) -> A +): Matcher = object : Matcher { + override val name: String = name + override fun get(receiver: S): A = get(receiver) + override fun invoke(receiver: S): A = get(receiver) +} diff --git a/arrow-libs/optics/arrow-match/src/jvmMain/kotlin/arrow/match/DoesNotMatch.kt b/arrow-libs/optics/arrow-match/src/jvmMain/kotlin/arrow/match/DoesNotMatch.kt new file mode 100644 index 0000000000..594a206e43 --- /dev/null +++ b/arrow-libs/optics/arrow-match/src/jvmMain/kotlin/arrow/match/DoesNotMatch.kt @@ -0,0 +1,6 @@ +package arrow.match + +public actual class DoesNotMatch: Throwable() { + // disable stacktrace creation + override fun fillInStackTrace(): Throwable = this +} diff --git a/arrow-libs/optics/arrow-match/src/jvmMain/kotlin/arrow/match/Matcher.kt b/arrow-libs/optics/arrow-match/src/jvmMain/kotlin/arrow/match/Matcher.kt new file mode 100644 index 0000000000..9218563ddd --- /dev/null +++ b/arrow-libs/optics/arrow-match/src/jvmMain/kotlin/arrow/match/Matcher.kt @@ -0,0 +1,45 @@ +package arrow.match + +import kotlin.reflect.KParameter +import kotlin.reflect.KProperty +import kotlin.reflect.KProperty1 +import kotlin.reflect.KType +import kotlin.reflect.KTypeParameter +import kotlin.reflect.KVisibility + +public actual fun Matcher( + name: String, + get: (S) -> A +): Matcher = object : Matcher, KProperty1.Getter { + override val name: String = name + override fun get(receiver: S): A = get(receiver) + override fun invoke(receiver: S): A = get(receiver) + + override val getter: KProperty1.Getter = this + override val property: KProperty = this + + override val annotations: List = emptyList() + override val isAbstract: Boolean = false + override val isConst: Boolean = false + override val isFinal: Boolean = false + override val isLateinit: Boolean = false + override val isOpen: Boolean = false + override val isSuspend: Boolean = false + override val isExternal: Boolean = false + override val isInfix: Boolean = false + override val isInline: Boolean = false + override val isOperator: Boolean = false + + override val parameters: List + get() = throw IllegalStateException("no parameters information") + override val returnType: KType + get() = throw IllegalStateException("no returnType information") + override val typeParameters: List = emptyList() + override val visibility: KVisibility? = null + + override fun call(vararg args: Any?): A = + get(args.single() as S) + override fun callBy(args: Map): A = + get(args.entries.single().value as S) + override fun getDelegate(receiver: S): Any? = null +} diff --git a/arrow-libs/optics/arrow-match/src/jvmTest/kotlin/arrow/match/Simple.kt b/arrow-libs/optics/arrow-match/src/jvmTest/kotlin/arrow/match/Simple.kt new file mode 100644 index 0000000000..bbbbb23e9e --- /dev/null +++ b/arrow-libs/optics/arrow-match/src/jvmTest/kotlin/arrow/match/Simple.kt @@ -0,0 +1,43 @@ +package arrow.match + +import io.kotest.matchers.shouldBe +import kotlin.test.Test + +data class Name( + val firstName: String, val lastName: String +) + +sealed interface User +data class Person( + val name: Name, val age: Int +) : User + +data class Company( + val name: String, val director: Name, val address: String +): User + +val User.shownName: String get() = this.matchOrThrow { + Person::class.of(Person::name.of(Name::firstName), Person::age.takeIf { it < 18 }) then { (fn, _) -> fn } + Person::class.of(Person::name.of(Name::firstName, Name::lastName)) then { (fn, ln) -> "Sir/Madam $fn $ln" } + Company::class.of(Company::name, Company::director.of(Name::lastName)) then { (nm, d) -> "$nm, att. $d" } +} + +val Person.shownNameForPerson: String get() = this.matchOrThrow { + it.of(Person::name.of(Name::firstName), Person::age.takeIf { it < 18 }) then { (fn, _) -> fn } + it.of(Person::name.of(Name::firstName, Name::lastName)) then { (fn, ln) -> "Sir/Madam $fn $ln" } +} + +class MatchTest { + @Test + fun userKid() { + val p = Person(Name("Jimmy", "Jones"), 7) + p.shownName shouldBe "Jimmy" + p.shownNameForPerson shouldBe "Jimmy" + } + + @Test fun userAdult() { + val p = Person(Name("Jimmy", "Jones"), 20) + p.shownName shouldBe "Sir/Madam Jimmy Jones" + p.shownNameForPerson shouldBe "Sir/Madam Jimmy Jones" + } +} diff --git a/arrow-libs/optics/arrow-match/src/nativeMain/kotlin/arrow/match/Matcher.kt b/arrow-libs/optics/arrow-match/src/nativeMain/kotlin/arrow/match/Matcher.kt new file mode 100644 index 0000000000..e6648f4f73 --- /dev/null +++ b/arrow-libs/optics/arrow-match/src/nativeMain/kotlin/arrow/match/Matcher.kt @@ -0,0 +1,15 @@ +package arrow.match + +import kotlin.reflect.KType + +public actual fun Matcher( + name: String, + get: (S) -> A +): Matcher = object : Matcher { + override val name: String = name + override fun get(receiver: S): A = get(receiver) + override fun invoke(receiver: S): A = get(receiver) + + override val returnType: KType + get() = TODO("Not yet implemented") +} diff --git a/arrow-libs/optics/arrow-match/src/nonJvmMain/kotlin/arrow/match/DoesNotMatch.kt b/arrow-libs/optics/arrow-match/src/nonJvmMain/kotlin/arrow/match/DoesNotMatch.kt new file mode 100644 index 0000000000..bf031c60c1 --- /dev/null +++ b/arrow-libs/optics/arrow-match/src/nonJvmMain/kotlin/arrow/match/DoesNotMatch.kt @@ -0,0 +1,3 @@ +package arrow.match + +public actual class DoesNotMatch: Throwable() diff --git a/arrow-libs/optics/arrow-match/src/wasmJsMain/kotlin/arrow/match/Matcher.kt b/arrow-libs/optics/arrow-match/src/wasmJsMain/kotlin/arrow/match/Matcher.kt new file mode 100644 index 0000000000..ad61c34100 --- /dev/null +++ b/arrow-libs/optics/arrow-match/src/wasmJsMain/kotlin/arrow/match/Matcher.kt @@ -0,0 +1,10 @@ +package arrow.match + +public actual fun Matcher( + name: String, + get: (S) -> A +): Matcher = object : Matcher { + override val name: String = name + override fun get(receiver: S): A = get(receiver) + override fun invoke(receiver: S): A = get(receiver) +} diff --git a/arrow-libs/optics/arrow-optics/api/arrow-optics.api b/arrow-libs/optics/arrow-optics/api/arrow-optics.api index c315e56695..0ed491abfb 100644 --- a/arrow-libs/optics/arrow-optics/api/arrow-optics.api +++ b/arrow-libs/optics/arrow-optics/api/arrow-optics.api @@ -548,7 +548,7 @@ public final class arrow/optics/match/CombinatorsKt { public static final fun it (Larrow/optics/POptional;Larrow/optics/POptional;Larrow/optics/POptional;Larrow/optics/POptional;)Larrow/optics/POptional; public static final fun it (Larrow/optics/POptional;Larrow/optics/POptional;Larrow/optics/POptional;Larrow/optics/POptional;Larrow/optics/POptional;)Larrow/optics/POptional; public static final fun predicate (Lkotlin/jvm/functions/Function1;)Larrow/optics/POptional; - public static final fun suchThat (Larrow/optics/POptional;Lkotlin/jvm/functions/Function1;)Larrow/optics/POptional; + public static final fun takeIf (Larrow/optics/POptional;Lkotlin/jvm/functions/Function1;)Larrow/optics/POptional; } public final class arrow/optics/match/MatchKt { diff --git a/arrow-libs/optics/arrow-optics/api/arrow-optics.klib.api b/arrow-libs/optics/arrow-optics/api/arrow-optics.klib.api index cd1fe8d942..11c7ac2521 100644 --- a/arrow-libs/optics/arrow-optics/api/arrow-optics.klib.api +++ b/arrow-libs/optics/arrow-optics/api/arrow-optics.klib.api @@ -370,7 +370,7 @@ final fun <#A: kotlin/Any?, #B: kotlin/Any?> (arrow.optics/PLens<#A, #A, kotlin. final fun <#A: kotlin/Any?, #B: kotlin/Any?> (arrow.optics/PLens<#B, #B, kotlin.collections/List<#A>, kotlin.collections/List<#A>>).arrow.optics/get(kotlin/Int): arrow.optics/POptional<#B, #B, #A, #A> // arrow.optics/get|get@arrow.optics.PLens<0:1,0:1,kotlin.collections.List<0:0>,kotlin.collections.List<0:0>>(kotlin.Int){0§;1§}[0] final fun <#A: kotlin/Any?, #B: kotlin/Any?> (arrow.optics/POptional<#A, #A, #B, #B>).arrow.optics.dsl/filter(kotlin/Function1<#B, kotlin/Boolean>): arrow.optics/POptional<#A, #A, #B, #B> // arrow.optics.dsl/filter|filter@arrow.optics.POptional<0:0,0:0,0:1,0:1>(kotlin.Function1<0:1,kotlin.Boolean>){0§;1§}[0] final fun <#A: kotlin/Any?, #B: kotlin/Any?> (arrow.optics/POptional<#A, #A, #B, #B>).arrow.optics.match/ifEquals(#B): arrow.optics/POptional<#A, #A, #B, #B> // arrow.optics.match/ifEquals|ifEquals@arrow.optics.POptional<0:0,0:0,0:1,0:1>(0:1){0§;1§}[0] -final fun <#A: kotlin/Any?, #B: kotlin/Any?> (arrow.optics/POptional<#A, #A, #B, #B>).arrow.optics.match/suchThat(kotlin/Function1<#B, kotlin/Boolean>): arrow.optics/POptional<#A, #A, #B, #B> // arrow.optics.match/suchThat|suchThat@arrow.optics.POptional<0:0,0:0,0:1,0:1>(kotlin.Function1<0:1,kotlin.Boolean>){0§;1§}[0] +final fun <#A: kotlin/Any?, #B: kotlin/Any?> (arrow.optics/POptional<#A, #A, #B, #B>).arrow.optics.match/takeIf(kotlin/Function1<#B, kotlin/Boolean>): arrow.optics/POptional<#A, #A, #B, #B> // arrow.optics.match/takeIf|takeIf@arrow.optics.POptional<0:0,0:0,0:1,0:1>(kotlin.Function1<0:1,kotlin.Boolean>){0§;1§}[0] final fun <#A: kotlin/Any?, #B: kotlin/Any?> (arrow.optics/POptional<#A, #A, arrow.core/NonEmptyList<#B>, arrow.core/NonEmptyList<#B>>).arrow.optics.dsl/index(kotlin/Int): arrow.optics/POptional<#A, #A, #B, #B> // arrow.optics.dsl/index|index@arrow.optics.POptional<0:0,0:0,arrow.core.NonEmptyList<0:1>,arrow.core.NonEmptyList<0:1>>(kotlin.Int){0§;1§}[0] final fun <#A: kotlin/Any?, #B: kotlin/Any?> (arrow.optics/POptional<#A, #A, kotlin.collections/List<#B>, kotlin.collections/List<#B>>).arrow.optics.dsl/index(kotlin/Int): arrow.optics/POptional<#A, #A, #B, #B> // arrow.optics.dsl/index|index@arrow.optics.POptional<0:0,0:0,kotlin.collections.List<0:1>,kotlin.collections.List<0:1>>(kotlin.Int){0§;1§}[0] final fun <#A: kotlin/Any?, #B: kotlin/Any?> (arrow.optics/POptional<#A, #A, kotlin.collections/Set<#B>, kotlin.collections/Set<#B>>).arrow.optics.dsl/at(#B): arrow.optics/POptional<#A, #A, kotlin/Boolean, kotlin/Boolean> // arrow.optics.dsl/at|at@arrow.optics.POptional<0:0,0:0,kotlin.collections.Set<0:1>,kotlin.collections.Set<0:1>>(0:1){0§;1§}[0] diff --git a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/match/Combinators.kt b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/match/Combinators.kt index 01f969bd31..aeb1701d7c 100644 --- a/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/match/Combinators.kt +++ b/arrow-libs/optics/arrow-optics/src/commonMain/kotlin/arrow/optics/match/Combinators.kt @@ -8,7 +8,7 @@ import arrow.core.raise.ensure import arrow.optics.Optional import arrow.optics.Prism -public fun Optional.suchThat( +public fun Optional.takeIf( predicate: (A) -> Boolean, ): Optional = this.compose(predicate(predicate)) @@ -24,7 +24,7 @@ public fun predicate( set = { x, _ -> x } ) -public fun Optional.ifEquals(value: A): Optional = this.suchThat { it == value } +public fun Optional.ifEquals(value: A): Optional = this.takeIf { it == value } public fun equalsTo(value: A): Optional = predicate { it == value } diff --git a/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/match/MatchTest.kt b/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/match/MatchTest.kt index 0f7dfc1cb7..cfcde0575f 100644 --- a/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/match/MatchTest.kt +++ b/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/match/MatchTest.kt @@ -62,13 +62,13 @@ data class Company( } val User.shownName: String get() = this.matchOrThrow { - User.person(Person.name(Name.firstName), Person.age.suchThat { it < 18 }) then { (fn, _) -> fn } + User.person(Person.name(Name.firstName), Person.age.takeIf { it < 18 }) then { (fn, _) -> fn } User.person(Person.name(Name.firstName, Name.lastName)) then { (fn, ln) -> "Sir/Madam $fn $ln" } User.company(Company.name, Company.director(Name.lastName)) then { (nm, d) -> "$nm, att. $d" } } val Person.shownNameForPerson: String get() = this.matchOrThrow { - it(Person.name(Name.firstName), Person.age.suchThat { it < 18 }) then { (fn, _) -> fn } + it(Person.name(Name.firstName), Person.age.takeIf { it < 18 }) then { (fn, _) -> fn } it(Person.name(Name.firstName, Name.lastName)) then { (fn, ln) -> "Sir/Madam $fn $ln" } } diff --git a/arrow-libs/stack/build.gradle.kts b/arrow-libs/stack/build.gradle.kts index fe20d48161..fdd58621d9 100644 --- a/arrow-libs/stack/build.gradle.kts +++ b/arrow-libs/stack/build.gradle.kts @@ -28,5 +28,6 @@ dependencies { api("io.arrow-kt:arrow-optics-compose:$version") api("io.arrow-kt:arrow-optics-reflect:$version") api("io.arrow-kt:arrow-optics-ksp-plugin:$version") + api("io.arrow-kt:arrow-match:$version") } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 8500aca867..cdeb2dcd5a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -108,6 +108,9 @@ project(":arrow-optics-compose").projectDir = file("arrow-libs/optics/arrow-opti include("arrow-optics-ksp-plugin") project(":arrow-optics-ksp-plugin").projectDir = file("arrow-libs/optics/arrow-optics-ksp-plugin") +include("arrow-match") +project(":arrow-match").projectDir = file("arrow-libs/optics/arrow-match") + // STACK include("arrow-stack") project(":arrow-stack").projectDir = file("arrow-libs/stack")