
jvmMain.arrow.core.composition-jvm.kt Maven / Gradle / Ivy
@file:JvmName("Composition")
package arrow.core
import kotlin.jvm.JvmName
public actual infix fun ((P1, P2) -> IP).andThen(f: (IP) -> R): (P1, P2) -> R =
AndThen2(this).andThen(f)
public actual infix fun (() -> IP).andThen(f: (IP) -> R): () -> R =
AndThen0(this).andThen(f)
public actual infix fun ((P1) -> IP).andThen(f: (IP) -> R): (P1) -> R =
AndThen1(this).andThen(f)
public actual infix fun ((IP) -> R).compose(f: (P1) -> IP): (P1) -> R =
AndThen1(this).compose(f)
/**
* Establishes the maximum stack depth when fusing `andThen` or `compose` calls.
*
* The default is `128`, from which we substract one as an
* optimization. This default has been reached like this:
*
* - according to official docs, the default stack size on 32-bits
* Windows and Linux was 320 KB, whereas for 64-bits it is 1024 KB
* - according to measurements chaining `Function1` references uses
* approximately 32 bytes of stack space on a 64 bits system;
* this could be lower if "compressed oops" is activated
* - therefore a "map fusion" that goes 128 in stack depth can use
* about 4 KB of stack space
*/
private const val maxStackDepthSize = 127
private sealed class AndThen0 : () -> A {
private data class Single(val f: () -> A, val index: Int) : AndThen0()
private data class Concat(val left: AndThen0, val right: AndThen1) : AndThen0() {
override fun toString(): String = "AndThen.Concat(...)"
}
fun andThen(g: (A) -> X): AndThen0 =
when (this) {
// Fusing calls up to a certain threshold
is Single ->
if (index != maxStackDepthSize) Single({ g(f()) }, index + 1)
else andThenF(AndThen1(g))
else -> andThenF(AndThen1(g))
}
@Suppress("UNCHECKED_CAST")
override fun invoke(): A =
loop(this as AndThen0, Unit, 0)
override fun toString(): String =
"AndThen0(...)"
companion object {
operator fun invoke(f: () -> A): AndThen0 =
when (f) {
is AndThen0 -> f
else -> Single(f, 0)
}
@Suppress("UNCHECKED_CAST")
tailrec fun loop(self: AndThen0, current: Any?, joins: Int): A = when (self) {
is Single -> if (joins == 0) self.f() as A else loop(self.f() as AndThen0, null, joins - 1)
is Concat<*, *> -> {
when (val oldLeft = self.left) {
is Single<*> -> {
val left = oldLeft as Single
val newSelf = self.right as AndThen1
AndThen1.loop(newSelf, left.f(), joins)
}
is Concat<*, *> -> loop(
rotateAccumulate(self.left as AndThen0, self.right as AndThen1),
current,
joins
)
}
}
}
@Suppress("UNCHECKED_CAST")
tailrec fun rotateAccumulate(
left: AndThen0,
right: AndThen1
): AndThen0 = when (left) {
is Concat<*, *> -> rotateAccumulate(
left.left as AndThen0,
(left.right as AndThen1).andThenF(right)
)
is Single<*> -> left.andThenF(right)
}
}
fun andThenF(right: AndThen1): AndThen0 =
Concat(this, right)
}
private sealed class AndThen1 : (A) -> B {
private data class Single(val f: (A) -> B, val index: Int) : AndThen1()
private data class Concat(val left: AndThen1, val right: AndThen1) : AndThen1() {
override fun toString(): String = "AndThen.Concat(...)"
}
fun andThen(g: (B) -> X): AndThen1 =
when (this) {
// Fusing calls up to a certain threshold
is Single ->
if (index != maxStackDepthSize) Single({ a: A -> g(this(a)) }, index + 1)
else andThenF(AndThen1(g))
else -> andThenF(AndThen1(g))
}
infix fun compose(g: (C) -> A): AndThen1 =
when (this) {
// Fusing calls up to a certain threshold
is Single ->
if (index != maxStackDepthSize) Single({ c: C -> this(g(c)) }, index + 1)
else composeF(AndThen1(g))
else -> composeF(AndThen1(g))
}
@Suppress("UNCHECKED_CAST")
override fun invoke(a: A): B = loop(this as AndThen1, a, 0)
override fun toString(): String = "AndThen(...)"
companion object {
operator fun invoke(f: (A) -> B): AndThen1 =
when (f) {
is AndThen1 -> f
else -> Single(f, 0)
}
@Suppress("UNCHECKED_CAST")
tailrec fun loop(self: AndThen1, current: Any?, joins: Int): B = when (self) {
is Single -> if (joins == 0) self.f(current) as B else loop(
self.f(current) as AndThen1,
null,
joins - 1
)
is Concat<*, *, *> -> {
when (val oldLeft = self.left) {
is Single<*, *> -> {
val left = oldLeft as Single
val newSelf = self.right as AndThen1
loop(newSelf, left.f(current), joins)
}
is Concat<*, *, *> -> loop(
rotateAccumulate(self.left as AndThen1, self.right as AndThen1),
current,
joins
)
}
}
}
@Suppress("UNCHECKED_CAST")
tailrec fun rotateAccumulate(
left: AndThen1,
right: AndThen1
): AndThen1 = when (left) {
is Concat<*, *, *> -> rotateAccumulate(
left.left as AndThen1,
(left.right as AndThen1).andThenF(right)
)
is Single<*, *> -> left.andThenF(right)
}
}
fun andThenF(right: AndThen1): AndThen1 =
Concat(this, right)
fun composeF(right: AndThen1): AndThen1 =
Concat(right, this)
}
private sealed class AndThen2 : (A, B) -> C {
private data class Single(val f: (A, B) -> C, val index: Int) : AndThen2()
private data class Concat(val left: AndThen2, val right: AndThen1) : AndThen2() {
override fun toString(): String = "AndThen.Concat(...)"
}
fun andThen(g: (C) -> X): AndThen2 =
when (this) {
// Fusing calls up to a certain threshold
is Single ->
if (index != maxStackDepthSize) Single({ a: A, b: B -> g(this(a, b)) }, index + 1)
else andThenF(AndThen1(g))
else -> andThenF(AndThen1(g))
}
@Suppress("UNCHECKED_CAST")
override fun invoke(a: A, b: B): C =
loop(this as AndThen2, a, b, 0)
override fun toString(): String = "AndThen(...)"
companion object {
operator fun invoke(f: (A, B) -> C): AndThen2 =
when (f) {
is AndThen2 -> f
else -> Single(f, 0)
}
@Suppress("UNCHECKED_CAST")
tailrec fun loop(self: AndThen2, currentA: Any?, currentB: Any?, joins: Int): C =
when (self) {
is Single<*, *, *> -> {
val f = self.f as ((Any?, Any?) -> Any?)
if (joins == 0) f(currentA, currentB) as C
else loop(f(currentA, currentB) as AndThen2, null, null, joins - 1)
}
is Concat<*, *, *, *> -> {
when (val oldLeft = self.left) {
is Single<*, *, *> -> {
val left = oldLeft as Single
val newSelf = self.right as AndThen1
AndThen1.loop(newSelf, left.f(currentA, currentB), joins)
}
is Concat<*, *, *, *> -> loop(
rotateAccumulate(self.left as AndThen2, self.right as AndThen1),
currentA,
currentB,
joins
)
}
}
}
@Suppress("UNCHECKED_CAST")
tailrec fun rotateAccumulate(
left: AndThen2,
right: AndThen1
): AndThen2 = when (left) {
is Concat<*, *, *, *> -> rotateAccumulate(
left.left as AndThen2,
(left.right as AndThen1).andThenF(right)
)
is Single<*, *, *> -> left.andThenF(right)
}
}
fun andThenF(right: AndThen1): AndThen2 =
Concat(this, right)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy