Skip to content

Commit

Permalink
Pattern matching with KClass and KProperty (#3449)
Browse files Browse the repository at this point in the history
* Pattern matching with KClass and KProperty

* Auto-update API files

* Suggestions by @nomisRev

---------

Co-authored-by: serras <[email protected]>
  • Loading branch information
serras and serras authored Oct 2, 2024
1 parent 3bd7890 commit 6ff4400
Show file tree
Hide file tree
Showing 19 changed files with 467 additions and 6 deletions.
42 changes: 42 additions & 0 deletions arrow-libs/optics/arrow-match/api/arrow-match.api
Original file line number Diff line number Diff line change
@@ -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 <init> ()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 <init> (Ljava/lang/Object;)V
public final fun getValue ()Ljava/lang/Object;
}

public abstract class arrow/match/MatchScope {
public fun <init> ()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;
}

51 changes: 51 additions & 0 deletions arrow-libs/optics/arrow-match/api/arrow-match.klib.api
Original file line number Diff line number Diff line change
@@ -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: <io.arrow-kt:arrow-match>
abstract class <#A: kotlin/Any?, #B: kotlin/Any?> arrow.match/MatchScope { // arrow.match/MatchScope|null[0]
constructor <init>() // arrow.match/MatchScope.<init>|<init>(){}[0]

final val it // arrow.match/MatchScope.it|{}it[0]
final fun <get-it>(): kotlin.reflect/KProperty1<#A, #A> // arrow.match/MatchScope.it.<get-it>|<get-it>(){}[0]

abstract fun <#A1: kotlin/Any?> (kotlin.reflect/KProperty1<#A, #A1>).then(kotlin/Function1<#A1, #B>) // arrow.match/MatchScope.then|[email protected]<1:0,0:0>(kotlin.Function1<0:0,1:1>){0§<kotlin.Any?>}[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|[email protected]<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§<kotlin.Any?>;2§<kotlin.Any?>;3§<kotlin.Any?>;4§<kotlin.Any?>;5§<kotlin.Any?>}[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|[email protected]<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§<kotlin.Any?>;2§<kotlin.Any?>;3§<kotlin.Any?>;4§<kotlin.Any?>}[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|[email protected]<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§<kotlin.Any?>;2§<kotlin.Any?>;3§<kotlin.Any?>}[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|[email protected]<0:0>(kotlin.reflect.KProperty1<0:0,0:1>;kotlin.reflect.KProperty1<0:0,0:2>){0§<1:0>;1§<kotlin.Any?>;2§<kotlin.Any?>}[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|[email protected]<0:0>(kotlin.reflect.KProperty1<0:0,0:1>){0§<1:0>;1§<kotlin.Any?>}[0]
}

final class arrow.match/DoesNotMatch : kotlin/Throwable { // arrow.match/DoesNotMatch|null[0]
constructor <init>() // arrow.match/DoesNotMatch.<init>|<init>(){}[0]
}

final class arrow.match/MatchNotFound : kotlin/Throwable { // arrow.match/MatchNotFound|null[0]
constructor <init>(kotlin/Any?) // arrow.match/MatchNotFound.<init>|<init>(kotlin.Any?){}[0]

final val value // arrow.match/MatchNotFound.value|{}value[0]
final fun <get-value>(): kotlin/Any? // arrow.match/MatchNotFound.value.<get-value>|<get-value>(){}[0]
}

final val arrow.match/isNotEmpty // arrow.match/isNotEmpty|@kotlin.reflect.KProperty1<0:0,kotlin.collections.Collection<0:1>>{0§<kotlin.Any?>;1§<kotlin.Any?>}isNotEmpty[0]
final fun <#A1: kotlin/Any?, #B1: kotlin/Any?> (kotlin.reflect/KProperty1<#A1, kotlin.collections/Collection<#B1>>).<get-isNotEmpty>(): kotlin.reflect/KProperty1<#A1, kotlin.collections/Collection<#B1>> // arrow.match/isNotEmpty.<get-isNotEmpty>|<get-isNotEmpty>@kotlin.reflect.KProperty1<0:0,kotlin.collections.Collection<0:1>>(){0§<kotlin.Any?>;1§<kotlin.Any?>}[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|[email protected]<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§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>;3§<kotlin.Any?>;4§<kotlin.Any?>;5§<kotlin.Any?>;6§<kotlin.Any?>}[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|[email protected]<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§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>;3§<kotlin.Any?>;4§<kotlin.Any?>;5§<kotlin.Any?>}[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|[email protected]<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§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>;3§<kotlin.Any?>;4§<kotlin.Any?>}[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|[email protected]<0:0,0:1>(kotlin.reflect.KProperty1<0:1,0:2>;kotlin.reflect.KProperty1<0:1,0:3>){0§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>;3§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?, #B: kotlin/Any?, #C: kotlin/Any?> (arrow.core.raise/SingletonRaise<#C>).arrow.match/matchOrRaise(#A, kotlin/Function1<arrow.match/MatchScope<#A, #B>, kotlin/Unit>): #B // arrow.match/matchOrRaise|[email protected]<0:2>(0:0;kotlin.Function1<arrow.match.MatchScope<0:0,0:1>,kotlin.Unit>){0§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>}[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|[email protected]<0:0,0:1>(kotlin.reflect.KProperty1<0:1,0:2>){0§<kotlin.Any?>;1§<kotlin.Any?>;2§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?, #B: kotlin/Any?> (#A).arrow.match/matchOrElse(kotlin/Function0<#B>, kotlin/Function1<arrow.match/MatchScope<#A, #B>, kotlin/Unit>): #B // arrow.match/matchOrElse|matchOrElse@0:0(kotlin.Function0<0:1>;kotlin.Function1<arrow.match.MatchScope<0:0,0:1>,kotlin.Unit>){0§<kotlin.Any?>;1§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?, #B: kotlin/Any?> (#A).arrow.match/matchOrThrow(kotlin/Function0<kotlin/Throwable> = ..., kotlin/Function1<arrow.match/MatchScope<#A, #B>, kotlin/Unit>): #B // arrow.match/matchOrThrow|matchOrThrow@0:0(kotlin.Function0<kotlin.Throwable>;kotlin.Function1<arrow.match.MatchScope<0:0,0:1>,kotlin.Unit>){0§<kotlin.Any?>;1§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?, #B: kotlin/Any?> (arrow.core.raise/Raise<arrow.match/MatchNotFound>).arrow.match/matchOrRaise(#A, kotlin/Function1<arrow.match/MatchScope<#A, #B>, kotlin/Unit>): #B // arrow.match/matchOrRaise|[email protected]<arrow.match.MatchNotFound>(0:0;kotlin.Function1<arrow.match.MatchScope<0:0,0:1>,kotlin.Unit>){0§<kotlin.Any?>;1§<kotlin.Any?>}[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|[email protected]<0:0,0:1>(kotlin.String?;kotlin.Function1<0:1,kotlin.Boolean>){0§<kotlin.Any?>;1§<kotlin.Any?>}[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§<kotlin.Any?>;1§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> (#A).arrow.match/matchUnit(kotlin/Function1<arrow.match/MatchScope<#A, kotlin/Unit>, kotlin/Unit>) // arrow.match/matchUnit|matchUnit@0:0(kotlin.Function1<arrow.match.MatchScope<0:0,kotlin.Unit>,kotlin.Unit>){0§<kotlin.Any?>}[0]
final fun <#A: kotlin/Any?> arrow.match/identity(): kotlin.reflect/KProperty1<#A, #A> // arrow.match/identity|identity(){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?, #B: reified #A> arrow.match/instanceOf(): kotlin.reflect/KProperty1<#A, #B> // arrow.match/instanceOf|instanceOf(){0§<kotlin.Any?>;1§<0:0>}[0]
50 changes: 50 additions & 0 deletions arrow-libs/optics/arrow-match/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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<KotlinCompile>("compileTestKotlinJvm") {
compilerOptions.freeCompilerArgs.add("-Xcontext-receivers")
}
2 changes: 2 additions & 0 deletions arrow-libs/optics/arrow-match/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Maven publishing configuration
POM_NAME=Arrow Match
Original file line number Diff line number Diff line change
@@ -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<S, A> = KProperty1<S, A>

public expect fun <S, A> Matcher(
name: String,
get: (S) -> A
): Matcher<S, A>

public expect class DoesNotMatch(): Throwable

public fun <S> identity(): Matcher<S, S> = Matcher("identity") { it }

public inline fun <S, reified A: S> instanceOf(): Matcher<S, A> =
Matcher("instanceOf<${A::class.simpleName}>") {
if (it is A) it else throw DoesNotMatch()
}

public fun <S, A> Matcher<S, A>.takeIf(
description: String? = null,
predicate: (A) -> Boolean
): Matcher<S, A> = Matcher("${this.name}.${description ?: "suchThat"}") {
val value = this.get(it)
if (predicate(value)) value else throw DoesNotMatch()
}

public val <S, A> Matcher<S, Collection<A>>.isNotEmpty: Matcher<S, Collection<A>>
get() = this.takeIf("isNotEmpty") { it.isNotEmpty() }

public fun <S, A, B> Matcher<S, A>.of(
field: Matcher<A, B>
): Matcher<S, B> = Matcher("${this.name}.of(${field.name})") {
field.get(this.get(it))
}

public fun <S, A, B, C> Matcher<S, A>.of(
field1: Matcher<A, B>,
field2: Matcher<A, C>
): Matcher<S, Pair<B, C>> = Matcher(
"${this.name}.of(${field1.name}, ${field2.name})"
) {
val a = this.get(it)
Pair(field1.get(a), field2.get(a))
}

public fun <S, A, B, C, D> Matcher<S, A>.of(
field1: Matcher<A, B>,
field2: Matcher<A, C>,
field3: Matcher<A, D>
): Matcher<S, Triple<B, C, D>> = 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 <S, A, B, C, D, E> Matcher<S, A>.of(
field1: Matcher<A, B>,
field2: Matcher<A, C>,
field3: Matcher<A, D>,
field4: Matcher<A, E>
): Matcher<S, Tuple4<B, C, D, E>> = 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 <S, A, B, C, D, E, F> Matcher<S, A>.of(
field1: Matcher<A, B>,
field2: Matcher<A, C>,
field3: Matcher<A, D>,
field4: Matcher<A, E>,
field5: Matcher<A, F>
): Matcher<S, Tuple5<B, C, D, E, F>> = 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))
}
Original file line number Diff line number Diff line change
@@ -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<S, R> {
public abstract infix fun <A> Matcher<S, A>.then(next: (A) -> R)
public abstract fun default(next: () -> R)

public val it: Matcher<S, S> = Matcher("it") { it }

public inline fun <reified A: S & Any, B> KClass<A>.of(
field: Matcher<A, B>
): Matcher<S, B> = instanceOf<S, A>().of(field)

public inline fun <reified A: S & Any, B, C> KClass<A>.of(
field1: Matcher<A, B>,
field2: Matcher<A, C>
): Matcher<S, Pair<B, C>> = instanceOf<S, A>().of(field1, field2)

public inline fun <reified A: S & Any, B, C, D> KClass<A>.of(
field1: Matcher<A, B>,
field2: Matcher<A, C>,
field3: Matcher<A, D>
): Matcher<S, Triple<B, C, D>> = instanceOf<S, A>().of(field1, field2, field3)

public inline fun <reified A: S & Any, B, C, D, E> KClass<A>.of(
field1: Matcher<A, B>,
field2: Matcher<A, C>,
field3: Matcher<A, D>,
field4: Matcher<A, E>
): Matcher<S, Tuple4<B, C, D, E>> = instanceOf<S, A>().of(field1, field2, field3, field4)

public inline fun <reified A: S & Any, B, C, D, E, F> KClass<A>.of(
field1: Matcher<A, B>,
field2: Matcher<A, C>,
field3: Matcher<A, D>,
field4: Matcher<A, E>,
field5: Matcher<A, F>
): Matcher<S, Tuple5<B, C, D, E, F>> = instanceOf<S, A>().of(field1, field2, field3, field4, field5)
}

private class MatchScopeImpl<S, R>(val subject: S): MatchScope<S, R>() {
override fun <A> Matcher<S, A>.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, R> S.matchOrElse(
noMatch: () -> R,
cases: MatchScope<S, R>.() -> 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, R> S.matchOrThrow(
exception: () -> Throwable = { MatchNotFound(this) },
cases: MatchScope<S, R>.() -> Unit,
): R = matchOrElse({ throw exception() }, cases)

public fun <S, R> Raise<MatchNotFound>.matchOrRaise(
value: S,
cases: MatchScope<S, R>.() -> Unit,
): R = value.matchOrElse({ raise(MatchNotFound(value)) }, cases)

public fun <S, R, E> SingletonRaise<E>.matchOrRaise(
value: S,
cases: MatchScope<S, R>.() -> Unit,
): R = value.matchOrElse({ raise() }, cases)

public fun <S> S.matchUnit(
cases: MatchScope<S, Unit>.() -> Unit,
): Unit = matchOrElse({ }, cases)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package arrow.match

@Suppress("IMPLEMENTING_FUNCTION_INTERFACE")
public actual fun <S, A> Matcher(
name: String,
get: (S) -> A
): Matcher<S, A> = object : Matcher<S, A> {
override val name: String = name
override fun get(receiver: S): A = get(receiver)
override fun invoke(receiver: S): A = get(receiver)
}
Loading

0 comments on commit 6ff4400

Please sign in to comment.