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

japgolly.scalajs.react.Reusable.scala Maven / Gradle / Ivy

The newest version!
package japgolly.scalajs.react

import japgolly.scalajs.react.internal.CoreGeneral._
import japgolly.scalajs.react.util.Effect.Sync
import japgolly.scalajs.react.util.{DefaultEffects => D}
import japgolly.scalajs.react.vdom.TagMod
import scala.reflect.ClassTag

/**
  * A value that has been explicitly paired with a (potentially ad-hoc) [[Reusability]] instance.
  *
  * @tparam A The type of value.
  * @since 1.0.0
  */
final class Reusable[+A] private[Reusable](lazyValue: () => A,
                                           private[Reusable] val root: Any,
                                           val isReusable: Reusable[Any] => Boolean) {
  override def toString = s"Reusable($value)"
  override def hashCode = value.##

  def value: A =
    // This always returns the same value and is actually referentially transparent and safe.
    // Doing this avoids applying modifications from .map in the event that this is reusable and
    // the value is never read.
    lazyValue()

  /** WARNING: This does not affect reusability.
    * Only the initial (pre-mapped) values matter when considering reusability.
    *
    * If you have two reusable values and map them differently, even though the mapped values differ they will still be
    * considered reusable. Any differences as a result of second the mapping will be discarded.
    */
  def map[B](f: A => B): Reusable[B] = {
    lazy val b = f(lazyValue())
    new Reusable[B](() => b, root, isReusable)
  }

  /** WARNING: This does not affect reusability.
    * Only the initial (pre-mapped) values matter when considering reusability.
    */
  def withValue[B](b: B): Reusable[B] =
    new Reusable[B](() => b, root, isReusable)

  /** WARNING: This does not affect reusability.
    * Only the initial (pre-mapped) values matter when considering reusability.
    */
  def withLazyValue[B](b: => B): Reusable[B] =
    map(_ => b)

  /** Create a new `Reusable[B]` that is reusable so long as this `Reusable[A]` and the `Reusable[A => B]` are. */
  def ap[B](rf: Reusable[A => B]): Reusable[B] =
    Reusable.ap(this, rf)((a, f) => f(a))

  /** Create a `Reusable[(A, B)]` that is reusable so long as this `Reusable[A]` and the `Reusable[B]` are. */
  def tuple[B](rb: Reusable[B]): Reusable[(A, B)] =
    Reusable.ap(this, rb)((_, _))

  /** Combine reusability of this and the argument, and return the value of this. */
  def <*[B](fb: Reusable[B]): Reusable[A] =
    Reusable.ap(this, fb)((a, _) => a)

  /** Combine reusability of this and the argument, and return the value of the argument. */
  def *>[B](fb: Reusable[B]): Reusable[B] =
    Reusable.ap(this, fb)((_, b) => b)

  /** WARNING: This does not affect reusability.
    * Only the initial (pre-mapped) values matter when considering reusability.
    */
  def sequenceOption[B](implicit ev: Reusable[A] <:< Reusable[Option[B]]): Option[Reusable[B]] = {
    val self = ev(this)
    self.value.map(withValue)
  }
}

object Reusable {

  @inline implicit def autoValue[A](r: Reusable[A]): A =
    r.value

  private def root[A](a: A, isReusable: Reusable[Any] => Boolean): Reusable[A] =
    new Reusable[A](() => a, a, isReusable)

  def apply[A: ClassTag](a: A)(reuse: (A, A) => Boolean): Reusable[A] =
    root(a, _.root match {
      case b: A => reuse(a, b)
      case _    => false
    })

  def implicitly[A: ClassTag : Reusability](a: A): Reusable[A] =
    explicitly(a)(Predef.implicitly)

  def explicitly[A: ClassTag](a: A)(r: Reusability[A]): Reusable[A] =
    apply(a)(r.test)

  /** Use constant reusability (i.e. always-reuse or never-reuse) */
  def const[A](a: A, isReusable: Boolean): Reusable[A] =
    root(a, _ => isReusable)

  def always[A](a: A): Reusable[A] =
    const(a, true)

  def never[A](a: A): Reusable[A] =
    const(a, false)

  lazy val unit: Reusable[Unit] =
    root((), _.root.isInstanceOf[Unit])

  /** Compare by reference. Reuse if both values are the same instance. */
  def byRef[A <: AnyRef](a: A): Reusable[A] =
    root(a, _.root match {
      case b: AnyRef => a eq b
      case _         => false
    })

  /** Compare by reference through an isomorphism. Reuse if both values are the same instance. */
  def byRefIso[A, B <: AnyRef](a: A)(iso: A => B): Reusable[A] = {
    val b = iso(a)
    new Reusable[A](() => a, b, _.root match {
      case x: AnyRef => b eq x
      case _         => false
    })
  }

  /** Compare using universal equality (Scala's == operator). */
  def by_==[A](a: A): Reusable[A] =
    root(a, a == _.root)

  /** Compare by reference and if different, compare using universal equality (Scala's == operator). */
  def byRefOr_==[A <: AnyRef](a: A): Reusable[A] =
    root(a, _.root match {
      case b: AnyRef => (a eq b) || (a == b)
      case _         => false
    })

  /** Create a new `Reusable[C]` that is reusable so long as `Reusable[A]` and `Reusable[B]` are. */
  def ap[A, B, C](ra: Reusable[A], rb: Reusable[B])(f: (A, B) => C): Reusable[C] =
    implicitly((ra, rb)).map(x => f(x._1, x._2))

  def sequenceOption[A](o: Option[Reusable[A]]): Reusable[Option[A]] =
    o match {
      case Some(ra) => ra.map(Some(_))
      case None     => unit.withValue(None)
    }

  private[this] val reusabilityInstance =
    Reusability[Reusable[Any]]((x, y) => x.isReusable(y) && y.isReusable(x))

  implicit def reusableReusability[A]: Reusability[Reusable[A]] =
    reusabilityInstance.narrow

  def reusabilityInstance[A](r: Reusability[A]): Reusable[Reusability[A]] =
    byRef(r.test).map(new Reusability(_))

  lazy val emptyVdom: Reusable[TagMod] =
    Reusable.byRef(TagMod.empty)

  // ===================================================================================================================

  /** Convenience methods that help you create `I => Reusable[O]` functions.
    *
    * The reason this is more convenient than the underlying methods is that this makes it easier to create and share a
    * single [[Reusability]] instance at the function definition site, before it's used.
    *
    * Also note, that it's important that the functions you provide are pure. If you don't always return the same output
    * given the same input, then you can expect different outputs to be discarded or "lost".
    *
    * @since 1.5.0
    */
  object fnOutput {

    def implicitly[I: ClassTag : Reusability, O](f: I => O): I => Reusable[O] =
      Reusable.implicitly(_).map(f)

    def explicitly[I: ClassTag, O](r: Reusability[I])(f: I => O): I => Reusable[O] =
      Reusable.explicitly(_)(r).map(f)

    /** Use constant reusability (i.e. always-reuse or never-reuse) */
    def const[I, O](f: I => O, isReusable: Boolean): I => Reusable[O] =
      Reusable.const(_, isReusable).map(f)

    def always[I, O](f: I => O): I => Reusable[O] =
      const(f, true)

    def never[I, O](f: I => O): I => Reusable[O] =
      const(f, false)

    /** Compare by reference. Reuse if both values are the same instance. */
    def byRef[I <: AnyRef, O](f: I => O): I => Reusable[O] =
      Reusable.byRef(_).map(f)

    /** Compare using universal equality (Scala's == operator). */
    def by_==[I, O](f: I => O): I => Reusable[O] =
      Reusable.by_==(_).map(f)

    /** Compare by reference and if different, compare using universal equality (Scala's == operator). */
    def byRefOr_==[I <: AnyRef, O](f: I => O): I => Reusable[O] =
      Reusable.byRefOr_==(_).map(f)
  }

  /**
   * A function that facilitates stability and reuse.
   *
   * In effective usage of React, callbacks are passed around as component properties.
   * Due to the ease of function creation in Scala it is often the case that functions are created inline and thus
   * provide no means of determining whether a component can safely skip its update.
   * This exists as a solution.
   *
   * @since 0.9.0
   */
  object fn {
    import FnInternals._

    def apply[Y, Z](f: Y => Z): Y ~=> Z =
      Reusable.implicitly(new Fn1(f))

    def apply[A: Reusability, Y, Z](f: (A, Y) => Z): A ~=> (Y ~=> Z) =
      Reusable.implicitly(new Fn2(f))

    def apply[A: Reusability, B: Reusability, Y, Z](f: (A, B, Y) => Z): A ~=> (B ~=> (Y ~=> Z)) =
      Reusable.implicitly(new Fn3(f))

    def apply[A: Reusability, B: Reusability, C: Reusability, Y, Z](f: (A, B, C, Y) => Z): A ~=> (B ~=> (C ~=> (Y ~=> Z))) =
      Reusable.implicitly(new Fn4(f))

    def apply[A: Reusability, B: Reusability, C: Reusability, D: Reusability, Y, Z](f: (A, B, C, D, Y) => Z): A ~=> (B ~=> (C ~=> (D ~=> (Y ~=> Z)))) =
      Reusable.implicitly(new Fn5(f))

    def apply[A: Reusability, B: Reusability, C: Reusability, D: Reusability, E: Reusability, Y, Z](f: (A, B, C, D, E, Y) => Z): A ~=> (B ~=> (C ~=> (D ~=> (E ~=> (Y ~=> Z))))) =
      Reusable.implicitly(new Fn6(f))

    def state[I, S](i: I)(implicit t: StateAccessor.WritePure[I, S]): StateAccessWriteOps[I, D.Sync, D.Async, S] =
      new StateAccessWriteOps(i)(t, D.Sync)

    final class StateAccessWriteOps[I, F[_], A[_], S](i: I)(implicit t: StateAccessor.Write[I, F, A, S], F: Sync[F]) {

      def setStateFn: Reusable[SetStateFn[F, A, S]] =
        Reusable.byRef(t(i).toSetStateFn)

      def modStateFn: Reusable[ModStateFn[F, A, S]] =
        Reusable.byRef(t(i).toModStateFn)

      def mod: (S => S) ~=> F[Unit] =
        Reusable.fn(t(i).modState(_))

      def modOption: (S => Option[S]) ~=> F[Unit] =
        Reusable.fn(t(i).modStateOption(_))

      def set: S ~=> F[Unit] =
        Reusable.fn(t(i).setState(_))

      def setOption: Option[S] ~=> F[Unit] =
        Reusable.fn(t(i).setStateOption(_))

      def modCB: Reusable[((S => S), F[Unit]) => F[Unit]] =
        Reusable.byRef(t(i).modState(_, _))

      def modOptionCB: Reusable[((S => Option[S]), F[Unit]) => F[Unit]] =
        Reusable.byRef(t(i).modStateOption(_, _))

      def setCB: Reusable[(S, F[Unit]) => F[Unit]] =
        Reusable.byRef(t(i).setState(_, _))

      def setOptionCB: Reusable[(Option[S], F[Unit]) => F[Unit]] =
        Reusable.byRef(t(i).setStateOption(_, _))
    }
  }

  // ===================================================================================================================

  private object FnInternals {
    private type R[A] = Reusability[A]
    type ReusableFn[-A, +B] = scala.runtime.AbstractFunction1[A, B]

    // -------------------------------------------------------------------------

    class Fn1[-Y, +Z](val f: Y => Z) extends ReusableFn[Y, Z] {
      override def apply(a: Y) = f(a)
    }

    private val _reusabilityFn1: Reusability[Fn1[_, _]] =
      Reusability((x, y) => (x eq y) || (x.f eq y.f))

    implicit def reusabilityFn1[Y, Z]: Reusability[Fn1[Y, Z]] =
      _reusabilityFn1.narrow

    // -------------------------------------------------------------------------

    class Fn2[A: R, -Y, +Z](val f: (A, Y) => Z) extends ReusableFn[A, Y ~=> Z] {
      override def apply(a: A) = Reusable.implicitly(new Cur2(a, f))
    }

    class Cur2[A, -Y, +Z](val a: A, val f: (A, Y) => Z) extends ReusableFn[Y, Z] {
      override def apply(y: Y): Z = f(a, y)
    }

    private val _reusabilityFn2: Reusability[Fn2[_, _, _]] =
      Reusability((x, y) => (x eq y) || (x.f eq y.f))

    implicit def reusabilityFn2[A, Y, Z]: Reusability[Fn2[A, Y, Z]] =
      _reusabilityFn2.narrow

    implicit def reusabilityCur2[A: R, Y, Z]: Reusability[Cur2[A, Y, Z]] =
      Reusability((x, y) => (x eq y) || ((x.f eq y.f) && (x.a ~=~ y.a)))

    // -------------------------------------------------------------------------

    class Fn3[A: R, B: R, -Y, +Z](val f: (A, B, Y) => Z) extends ReusableFn[A, B ~=> (Y ~=> Z)] {
      private val c2 = cur3(f)
      override def apply(a: A) = Reusable.implicitly(new Cur2(a, c2))
    }

    def cur3[A: R, B: R, Y, Z](f: (A, B, Y) => Z): (A, B) => (Y ~=> Z) =
      (a, b) => Reusable.implicitly(new Cur3(a, b, f))

    class Cur3[A, B, -Y, +Z](val a: A, val b: B, val f: (A, B, Y) => Z) extends ReusableFn[Y, Z] {
      override def apply(y: Y): Z = f(a, b, y)
    }

    private val _reusabilityFn3: Reusability[Fn3[_, _, _, _]] =
      Reusability((x, y) => (x eq y) || (x.f eq y.f))

    implicit def reusabilityFn3[A, B, Y, Z]: Reusability[Fn3[A, B, Y, Z]] =
      _reusabilityFn3.narrow

    implicit def reusabilityCur3[A: R, B: R, Y, Z]: Reusability[Cur3[A, B, Y, Z]] =
      Reusability((x, y) => (x eq y) || ((x.f eq y.f) && (x.a ~=~ y.a) && (x.b ~=~ y.b)))

    // -------------------------------------------------------------------------

    class Fn4[A: R, B: R, C: R, -Y, +Z](val f: (A, B, C, Y) => Z) extends ReusableFn[A, B ~=> (C ~=> (Y ~=> Z))] {
      private val c3 = cur4(f)
      private val c2 = cur3(c3)
      override def apply(a: A) = Reusable.implicitly(new Cur2(a, c2))
    }

    def cur4[A: R, B: R, C: R, Y, Z](f: (A, B, C, Y) => Z): (A, B, C) => (Y ~=> Z) =
      (a, b, c) => Reusable.implicitly(new Cur4(a, b, c, f))

    class Cur4[A, B, C, -Y, +Z](val a: A, val b: B, val c: C, val f: (A, B, C, Y) => Z) extends ReusableFn[Y, Z] {
      override def apply(y: Y): Z = f(a, b, c, y)
    }

    implicit def reusabilityFn4[A, B, C, Y, Z]: Reusability[Fn4[A, B, C, Y, Z]] =
      Reusability((x, y) => (x eq y) || (x.f eq y.f))

    implicit def reusabilityCur4[A: R, B: R, C: R, Y, Z]: Reusability[Cur4[A, B, C, Y, Z]] =
      Reusability((x, y) => (x eq y) || ((x.f eq y.f) && (x.a ~=~ y.a) && (x.b ~=~ y.b) && (x.c ~=~ y.c)))

    // -------------------------------------------------------------------------

    class Fn5[A: R, B: R, C: R, D: R, -Y, +Z](val f: (A, B, C, D, Y) => Z) extends ReusableFn[A, B ~=> (C ~=> (D ~=> (Y ~=> Z)))] {
      private val c4 = cur5(f)
      private val c3 = cur4(c4)
      private val c2 = cur3(c3)
      override def apply(a: A) = Reusable.implicitly(new Cur2(a, c2))
    }

    def cur5[A: R, B: R, C: R, D: R, Y, Z](f: (A, B, C, D, Y) => Z): (A, B, C, D) => (Y ~=> Z) =
      (a, b, c, d) => Reusable.implicitly(new Cur5(a, b, c, d, f))

    class Cur5[A, B, C, D, -Y, +Z](val a: A, val b: B, val c: C, val d: D, val f: (A, B, C, D, Y) => Z) extends ReusableFn[Y, Z] {
      override def apply(y: Y): Z = f(a, b, c, d, y)
    }

    implicit def reusabilityFn5[A, B, C, D, Y, Z]: Reusability[Fn5[A, B, C, D, Y, Z]] =
      Reusability((x, y) => (x eq y) || (x.f eq y.f))

    implicit def reusabilityCur5[A: R, B: R, C: R, D: R, Y, Z]: Reusability[Cur5[A, B, C, D, Y, Z]] =
      Reusability((x, y) => (x eq y) || ((x.f eq y.f) && (x.a ~=~ y.a) && (x.b ~=~ y.b) && (x.c ~=~ y.c) && (x.d ~=~ y.d)))

    // -------------------------------------------------------------------------

    class Fn6[A: R, B: R, C: R, D: R, E: R, -Y, +Z](val f: (A, B, C, D, E, Y) => Z) extends ReusableFn[A, B ~=> (C ~=> (D ~=> (E ~=> (Y ~=> Z))))] {
      private val c5 = cur6(f)
      private val c4 = cur5(c5)
      private val c3 = cur4(c4)
      private val c2 = cur3(c3)
      override def apply(a: A) = Reusable.implicitly(new Cur2(a, c2))
    }

    def cur6[A: R, B: R, C: R, D: R, E: R, Y, Z](f: (A, B, C, D, E, Y) => Z): (A, B, C, D, E) => (Y ~=> Z) =
      (a, b, c, d, e) => Reusable.implicitly(new Cur6(a, b, c, d, e, f))

    class Cur6[A, B, C, D, E, -Y, +Z](val a: A, val b: B, val c: C, val d: D, val e: E, val f: (A, B, C, D, E, Y) => Z) extends ReusableFn[Y, Z] {
      override def apply(y: Y): Z = f(a, b, c, d, e, y)
    }

    implicit def reusabilityFn6[A, B, C, D, E, Y, Z]: Reusability[Fn6[A, B, C, D, E, Y, Z]] =
      Reusability((x, y) => (x eq y) || (x.f eq y.f))

    implicit def reusabilityCur6[A: R, B: R, C: R, D: R, E: R, Y, Z]: Reusability[Cur6[A, B, C, D, E, Y, Z]] =
      Reusability((x, y) => (x eq y) || ((x.f eq y.f) && (x.a ~=~ y.a) && (x.b ~=~ y.b) && (x.c ~=~ y.c) && (x.d ~=~ y.d) && (x.e ~=~ y.e)))
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy