All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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)
}