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

doodle.interact.animation.Transducer.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015 Creative Scala
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package doodle
package interact
package animation

import cats.*
import cats.effect.unsafe.IORuntime
import cats.implicits.*
import doodle.algebra.Algebra
import doodle.algebra.Picture
import doodle.effect.Renderer
import doodle.interact.algebra.Redraw
import doodle.interact.effect.AnimationRenderer
import doodle.interact.syntax.animationRenderer.*
import fs2.Pure
import fs2.Stream

import scala.annotation.tailrec

/** A Transducer represents an animation that starts in some initial state and
  * proceeds through various states until it stops. For example, a square might
  * move from an x position of 0 to an x position of 100 in increments of 10.
  * The states then become 0, 10, 20, ..., 100.
  *
  * More abstractly, a transducer is a finite state machine with an additional
  * set of output values, so that each state is associated with some output
  * value. Continuing the example of the moving square, the states are the x
  * position and the output is the square at the given position (for some fixed
  * y coordinate).
  *
  * Transducers should be treated like half open intervals, which means they
  * should generate the inital state but avoid generating a stopping state when
  * possible (for example, when combined in sequence with another transducer).
  *
  * Transducers have several type classes instances:
  *   - Traverse
  *   - Applicative
  *   - Monoid, corresponding to sequential composition (++)
  *
  * The majority of the API is provided by the Cats methods defined on these
  * type classes, so import cats.implicits._ to get a richer API (e.g. toList,
  * filter, etc.)
  *
  * There is another possible monoid, which corresponds to parallel composition
  * (and), if there is a monoid on the type A. Taken with ++ this makes
  * Transducers a Rig or Semiring (depending on how one defines these terms;
  * they are not always defined in the same way).
  */
trait Transducer[Output] { self =>

  /** The type of the state used by this transducer. A type parameter as this
    * isn't really needed outside of the transducer.
    */
  type State

  /** The initial state for this Transducer.
    */
  def initial: State

  /** A method that constructs the next state given the current state. If the
    * current state is a stopped state this method should always return that
    * state.
    */
  def next(current: State): State

  /** A method that returns the output of the current state. If the transducer
    * has stopped it may not have any output, in which case it can throw a
    * java.util.NoSuchElementException. As a result, clients should avoid
    * calling this method when the transducer is in a stopped state. If possible
    * this method should return some other sensible result, rather than throwing
    * an exception, if given a stopped state.
    */
  def output(state: State): Output

  /** True if this state is a stopped (or halt) state, meaning the transducer
    * will never transition to a different state.
    */
  def stopped(state: State): Boolean

  /** Transform the output of this transducer using the given function
    */
  def map[B](f: Output => B): Transducer[B] =
    new Transducer[B] {
      type State = self.State

      val initial: State = self.initial
      def next(current: State): State =
        self.next(current)
      def output(state: State): B =
        f(self.output(state))
      def stopped(state: State): Boolean =
        self.stopped(state)
    }

  /** Append that transducer to this transducer, so that tranducer runs when
    * this one has finished. Both transducers must produce output of the same
    * type.
    */
  def ++(that: Transducer[Output]): Transducer[Output] =
    new Transducer[Output] {
      type State = Either[self.State, that.State]

      val initial: State =
        if self.stopped(self.initial) then that.initial.asRight
        else self.initial.asLeft

      def next(current: State): State =
        current match {
          case Left(a) =>
            val nextA = self.next(a)
            if self.stopped(nextA) then that.initial.asRight
            else nextA.asLeft
          case Right(b) => that.next(b).asRight
        }

      def output(state: State): Output =
        state match {
          case Left(a)  => self.output(a)
          case Right(b) => that.output(b)
        }

      def stopped(state: State): Boolean =
        state match {
          case Left(_)  => false
          case Right(b) => that.stopped(b)
        }
    }

  /** When this transducer's next state would be a stopped state, transition to
    * the tranducer created by calling the given function with the current
    * output. If this transducer immediately stops, and hence has no output,
    * there will be no output to pass to the function and therefore the next
    * transducer will not be created.
    *
    * This is like append (++) but allows the final output to determine the
    * transducer that is appended.
    */
  def andThen(f: Output => Transducer[Output]): Transducer[Output] = {
    import Transducer.*

    new Transducer[Output] {
      type State = (Boolean, Box[?, Output])

      val initial = (true, Box[self.State, Output](self)(self.initial))

      def next(current: State): State = {
        current match {
          case (first, box: Box[s, Output]) =>
            val nextS = box.next
            if first && box.transducer.stopped(nextS) then {
              val nextT = f(box.output)
              (false, Box[nextT.State, Output](nextT)(nextT.initial))
            } else {
              (first, Box(box.transducer)(nextS))
            }
        }
      }

      def output(state: (Boolean, Transducer.Box[?, Output])): Output =
        state match {
          case (_, box: Box[s, Output]) => box.output
        }

      def stopped(state: (Boolean, Transducer.Box[?, Output])): Boolean =
        state match {
          case (_, box: Box[s, Output]) => box.stopped
        }
    }
  }

  /** Create a transducer that runs this transducer in parallel with that
    * transducer, stopping when both have stopped. Both transducers must produce
    * output of the same type, and there must be a monoid instance for the
    * output type.
    *
    * If one transducer stops before the other then its last output before
    * stopping is returned as its output until the other transducer stops. If it
    * stops before generating output (i.e. its initial state is a stopping
    * state) than the zero / identity of the monoid is used as its output. This
    * behaviour is usually what we want for animations, and it makes and a
    * monoid instance for transducer with empty as the identity. To stop when
    * either have stopped see [[product]].
    */
  def and(
      that: Transducer[Output]
  )(implicit m: Monoid[Output]): Transducer[Output] =
    new Transducer[Output] {
      type State = (self.State, Output, that.State, Output)

      val initial: State = (
        self.initial,
        if self.stopped(self.initial) then m.empty
        else self.output(self.initial),
        that.initial,
        if that.stopped(that.initial) then m.empty
        else that.output(that.initial)
      )

      def next(current: State): State = {
        val (a, ao, b, bo) = current
        val nextA = self.next(a)
        val nextB = that.next(b)

        val nextAO = if self.stopped(nextA) then ao else self.output(nextA)
        val nextBO = if that.stopped(nextB) then bo else that.output(nextB)

        (nextA, nextAO, nextB, nextBO)
      }

      def output(state: State): Output = {
        val (_, ao, _, bo) = state
        ao |+| bo
      }

      def stopped(state: State): Boolean = {
        val (a, _, b, _) = state
        self.stopped(a) && that.stopped(b)
      }
    }

  /** Create a transducer that runs this transducer in parallel with that
    * transducer, stopping when either has stopped. To stop when both have
    * stopped see [[and]].
    */
  def product[B](that: Transducer[B]): Transducer[(Output, B)] =
    new Transducer[(Output, B)] {
      type State = (self.State, that.State)

      val initial: State = (self.initial, that.initial)

      def next(current: State): State = {
        val (a, b) = current
        (self.next(a), that.next(b))
      }

      def output(state: State): (Output, B) = {
        val (a, b) = state
        (self.output(a), that.output(b))
      }

      def stopped(state: State): Boolean = {
        val (a, b) = state
        self.stopped(a) || that.stopped(b)
      }
    }

  /** Create a transducer that outputs the cumulative results of applying the
    * function f to the output of the underlying transducer. If the underlying
    * transducer has stopped the zero value is produced as the only output.
    */
  def scanLeft[B](zero: B)(f: (B, Output) => B): Transducer[B] =
    new Transducer[B] {
      // State consists of
      // - state of the underlying transducer,
      //
      // - the current cumulative result,
      //
      // - flag indicating if the transducer has stopped (which happens one step
      //   after the underlying transducer stops)
      type State = (self.State, B, Boolean)

      val initial: State = (self.initial, zero, false)

      def next(current: State): State = {
        val (a, b, flag) = current
        if flag then current
        else if self.stopped(a) then (a, b, true)
        else (self.next(a), f(b, self.output(a)), false)
      }

      def output(state: State): B = {
        val (_, b, _) = state
        b
      }

      def stopped(state: State): Boolean = {
        val (_, _, flag) = state
        flag
      }
    }

  def foldLeft[B](zero: B)(f: (B, Output) => B): B = {
    @tailrec def loop(b: B, state: State): B =
      if self.stopped(state) then b
      else loop(f(b, self.output(state)), self.next(state))

    loop(zero, this.initial)
  }

  def foldRight[B](zero: Eval[B])(f: (Output, Eval[B]) => Eval[B]): Eval[B] = {
    def loop(state: State): Eval[B] =
      if self.stopped(state) then zero
      else f(self.output(state), loop(self.next(state)))

    loop(self.initial)
  }

  def traverse[G[_], B](
      f: Output => G[B]
  )(implicit G: Applicative[G]): G[Transducer[B]] =
    self
      .foldRight[G[Transducer[B]]](Always(G.pure(Transducer.empty))) {
        (output, gtb) =>
          G.map2Eval(f(output), gtb)(Transducer.pure(_) ++ _)
      }
      .value

  /** Construct a transducer by appending this transducer to itself the given
    * number of times.
    *
    * The count must be 0 or greater.
    */
  def repeat(count: Int): Transducer[Output] = {
    assert(
      count >= 0,
      s"A transducer must be repeated 0 or more times. Given $count as the number of repeats."
    )
    0.until(count).foldLeft(Transducer.empty[Output])((t, _) => t.++(this))
  }

  def repeatForever: Transducer[Output] =
    new Transducer[Output] {
      type State = self.State

      def initial: State = self.initial

      def next(current: State): State = {
        val next = self.next(current)
        if self.stopped(next) then self.initial
        else next
      }

      def output(state: State): Output =
        self.output(state)

      def stopped(state: State): Boolean = false
    }

  /** Convert this transducer to a fs2.Stream
    */
  def toStream: Stream[Pure, Output] =
    Stream.unfold(self.initial) { state =>
      if self.stopped(state) then None
      else Some((self.output(state), self.next(state)))
    }

  /** Convenience method to animate a transducer.
    */
  def animate[Alg <: Algebra, Frame, Canvas](frame: Frame)(implicit
      a: AnimationRenderer[Canvas],
      e: Renderer[Alg, Frame, Canvas],
      r: Redraw[Canvas],
      ev: Output <:< Picture[Alg, Unit],
      runtime: IORuntime
  ): Unit =
    this.toStream.map(ev(_)).animateFrames(frame)
}
object Transducer {

  /** Aux instance for Transducer. Use when you need to make the State type
    * visible as a type parameter.
    */
  type Aux[S0, O0] = Transducer[O0] { type State = S0 }

  /** This associates a transducer with its state, useful to get around issues
    * with inference of existential types.
    */
  final case class Box[S, O](transducer: Transducer.Aux[S, O])(state: S) {
    self =>
    def next: S =
      transducer.next(state)

    def output: O =
      transducer.output(state)

    def stopped: Boolean =
      transducer.stopped(state)
  }

  implicit val transducerTraverseAndApplicative
      : Traverse[Transducer] with Applicative[Transducer] =
    new Traverse[Transducer] with Applicative[Transducer] {
      def foldLeft[A, B](fa: Transducer[A], b: B)(f: (B, A) => B): B =
        fa.foldLeft(b)(f)

      def foldRight[A, B](fa: Transducer[A], lb: Eval[B])(
          f: (A, Eval[B]) => Eval[B]
      ): Eval[B] =
        fa.foldRight(lb)(f)

      def traverse[G[_], A, B](
          fa: Transducer[A]
      )(f: (A) => G[B])(implicit arg0: Applicative[G]): G[Transducer[B]] =
        fa.traverse(f)(arg0)

      def ap[A, B](ff: Transducer[A => B])(fa: Transducer[A]): Transducer[B] =
        ff.product(fa).map { case (f, a) => f(a) }

      def pure[A](x: A): Transducer[A] =
        Transducer.pure(x)

      override def map[A, B](fa: Transducer[A])(f: A => B): Transducer[B] =
        fa.map(f)
    }

  implicit def transducerMonoid[A]: Monoid[Transducer[A]] =
    new Monoid[Transducer[A]] {
      def combine(x: Transducer[A], y: Transducer[A]): Transducer[A] =
        x.++(y)

      def empty: Transducer[A] =
        Transducer.empty[A]
    }

  /** Create a transducer that immediately stops. */
  def empty[A]: Transducer[A] =
    apply()

  /** Create a transducer that produces the given element and then stops. */
  def pure[A](elt: A): Transducer[A] =
    apply(elt)

  /** Create a transducer that produces elements in-order from the given list
    * and then stops.
    */
  def fromList[A](elts: List[A]): Transducer[A] =
    apply(elts*)

  /** Create a transducer that produces the given elements in-order and then
    * stops.
    */
  def apply[A](elts: A*): Transducer[A] =
    new Transducer[A] {
      type State = Seq[A]

      val initial: State = elts

      def next(current: State): State =
        if current.isEmpty then current
        else current.tail

      def output(state: State): A =
        if state.isEmpty then
          throw new NoSuchElementException(
            "This transducer has no more output."
          )
        else state.head

      def stopped(state: State): Boolean =
        state match {
          case Seq() => true
          case _     => false
        }
    }

  /** Create a transducer that starts with given `start` state and runs forever.
    * The function `update` is used to update the state.
    */
  def scanLeft[A](start: A)(update: A => A): Transducer[A] =
    new Transducer[A] {
      type State = A

      val initial: State = start

      def next(current: State): State =
        update(current)

      def output(state: State): A =
        state

      def stopped(state: State): Boolean =
        false
    }

  /** Create a transducer that starts with given `start` state, and runs until
    * `stop` returns `false`. The function `f` is used to update the state.
    */
  def scanLeftUntil[A](
      start: A
  )(update: A => A)(stop: A => Boolean): Transducer[A] =
    new Transducer[A] {
      type State = A

      val initial: State = start

      def next(current: State): State =
        update(current)

      def output(state: State): A =
        state

      def stopped(state: State): Boolean =
        stop(state)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy