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

cats.data.StreamingT.scala Maven / Gradle / Ivy

package cats
package data

import cats.syntax.all._

/**
 * StreamingT[F, A] is a monad transformer which parallels Streaming[A].
 *
 * However, there are a few key differences. `StreamingT[F, A]` only
 * supports lazy evaluation if `F` does, and if the `F[_]` values are
 * constructed lazily. Also, monadic recursion on `StreamingT[F, A]`
 * is stack-safe only if monadic recursion on `F` is stack-safe.
 * Finally, since `F` is not guaranteed to have a `Comonad`, it does
 * not support many methods on `Streaming[A]` which return immediate
 * values.
 */
sealed abstract class StreamingT[F[_], A] { lhs =>

  import StreamingT.{Empty, Wait, Cons}

  /**
   * Deconstruct a stream into a head and tail (if available).
   *
   * This method will evaluate the stream until it finds a head and
   * tail, or until the stream is exhausted.
   */
  def uncons(implicit ev: Monad[F]): F[Option[(A, F[StreamingT[F, A]])]] =
    this match {
      case Cons(a, ft) => ev.pure(Some((a, ft)))
      case Wait(ft) => ft.flatMap(_.uncons)
      case Empty() => ev.pure(None)
    }

  /**
   * Lazily transform the stream given a function `f`.
   */
  def map[B](f: A => B)(implicit ev: Functor[F]): StreamingT[F, B] =
    this match {
      case Cons(a, ft) => Cons(f(a), ft.map(_.map(f)))
      case Wait(ft) => Wait(ft.map(_.map(f)))
      case Empty() => Empty()
    }

  /**
   * Lazily transform the stream given a function `f`.
   */
  def flatMap[B](f: A => StreamingT[F, B])(implicit ev: Monad[F]): StreamingT[F, B] = {
    this match {
      case Cons(a, ft) =>
        Wait(f(a) fconcat ft.map(_.flatMap(f)))
      case Wait(ft) =>
        Wait(ft.map(_.flatMap(f)))
      case Empty() =>
        Empty()
    }
  }

  /**
   * xyz
   */
  def coflatMap[B](f: StreamingT[F, A] => B)(implicit ev: Functor[F]): StreamingT[F, B] =
    this match {
      case Cons(a, ft) => Cons(f(this), ft.map(_.coflatMap(f)))
      case Wait(ft) => Wait(ft.map(_.coflatMap(f)))
      case Empty() => Empty()
    }

  /**
   * Lazily filter the stream given the predicate `f`.
   */
  def filter(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] =
    this match {
      case Cons(a, ft) => if (f(a)) this else Wait(ft.map(_.filter(f)))
      case Wait(ft) => Wait(ft.map(_.filter(f)))
      case Empty() => this
    }

  /**
   * Eagerly fold the stream to a single value from the left.
   */
  def foldLeft[B](b: B)(f: (B, A) => B)(implicit ev: Monad[F]): F[B] =
    this match {
      case Cons(a, ft) => ft.flatMap(_.foldLeft(f(b, a))(f))
      case Wait(ft) => ft.flatMap(_.foldLeft(b)(f))
      case Empty() => ev.pure(b)
    }

  /**
   * Eagerly search the stream from the left. The search ends when f
   * returns true for some a, or the stream is exhausted. Some(a)
   * signals a match, None means no matching items were found.
   */
  def find(f: A => Boolean)(implicit ev: Monad[F]): F[Option[A]] =
    this match {
      case Cons(a, ft) =>
        if (f(a)) ev.pure(Some(a)) else ft.flatMap(_.find(f))
      case Wait(ft) =>
        ft.flatMap(_.find(f))
      case Empty() =>
        ev.pure(None)
    }

  /**
   * Return true if the stream is empty, false otherwise.
   *
   * In this case of deferred streams this will force the first
   * element to be calculated.
   */
  def isEmpty(implicit ev: Monad[F]): F[Boolean] =
    uncons.map(_.isDefined)

  /**
   * Return true if the stream is non-empty, false otherwise.
   *
   * In this case of deferred streams this will force the first
   * element to be calculated.
   */
  def nonEmpty(implicit ev: Monad[F]): F[Boolean] =
    uncons.map(_.isEmpty)

  /**
   * Prepend an A value to the current stream.
   */
  def %::(a: A)(implicit ev: Applicative[F]): StreamingT[F, A] =
    Cons(a, ev.pure(this))

  /**
   * Prepend a StreamingT[F, A] value to the current stream.
   */
  def %:::(lhs: StreamingT[F, A])(implicit ev: Functor[F]): StreamingT[F, A] =
    lhs match {
      case Cons(a, ft) => Cons(a, ft.map(_ %::: this))
      case Wait(ft) => Wait(ft.map(_ %::: this))
      case Empty() => this
    }

  /**
   * Concatenate streaming values within F[_].
   *
   * This method is useful when calling .flatMap over a
   * F[StreamingT[F, A]] value.
   */
  def concat(rhs: F[StreamingT[F, A]])(implicit ev: Monad[F]): StreamingT[F, A] =
    this match {
      case Cons(a, ft) => Cons(a, ft.flatMap(_ fconcat rhs))
      case Wait(ft) => Wait(ft.flatMap(_ fconcat rhs))
      case Empty() => Wait(rhs)
    }

  /**
   * Concatenate streaming values within F[_].
   *
   * This method is useful when calling .flatMap over a
   * F[StreamingT[F, A]] value.
   */
  def fconcat(rhs: F[StreamingT[F, A]])(implicit ev: Monad[F]): F[StreamingT[F, A]] =
    this match {
      case Cons(a, ft) => ev.pure(Cons(a, ft.flatMap(_ fconcat rhs)))
      case Wait(ft) => ft.flatMap(_ fconcat rhs)
      case Empty() => rhs
    }

  /**
   * Return true if some element of the stream satisfies the
   * predicate, false otherwise.
   */
  def exists(f: A => Boolean)(implicit ev: Monad[F]): F[Boolean] =
    this match {
      case Cons(a, ft) => if (f(a)) ev.pure(true) else ft.flatMap(_.exists(f))
      case Wait(ft) => ft.flatMap(_.exists(f))
      case Empty() => ev.pure(false)
    }

  /**
   * Return true if every element of the stream satisfies the
   * predicate, false otherwise.
   */
  def forall(f: A => Boolean)(implicit ev: Monad[F]): F[Boolean] =
    this match {
      case Cons(a, ft) => if (!f(a)) ev.pure(false) else ft.flatMap(_.forall(f))
      case Wait(ft) => ft.flatMap(_.forall(f))
      case Empty() => ev.pure(true)
    }

  /**
   * Return a stream consisting only of the first `n` elements of this
   * stream.
   *
   * If the current stream has `n` or fewer elements, the entire
   * stream will be returned.
   */
  def take(n: Int)(implicit ev: Functor[F]): StreamingT[F, A] =
    if (n <= 0) Empty() else this match {
      case Cons(a, ft) => Cons(a, ft.map(_.take(n - 1)))
      case Wait(ft) => Wait(ft.map(_.take(n)))
      case Empty() => Empty()
    }

  /**
   * Return a stream consisting of all but the first `n` elements of
   * this stream.
   *
   * If the current stream has `n` or fewer elements, an empty stream
   * will be returned.
   */
  def drop(n: Int)(implicit ev: Functor[F]): StreamingT[F, A] =
    if (n <= 0) this else this match {
      case Cons(a, ft) => Wait(ft.map(_.take(n - 1)))
      case Wait(ft) => Wait(ft.map(_.drop(n)))
      case Empty() => Empty()
    }

  /**
   * From the beginning of this stream, create a new stream which
   * takes only those elements that fulfill the predicate `f`. Once an
   * element is found which does not fulfill the predicate, no further
   * elements will be returned.
   *
   * If all elements satisfy `f`, the current stream will be returned.
   * If no elements satisfy `f`, an empty stream will be returned.
   *
   * For example:
   *
   *   StreamingT[List, Int](1, 2, 3, 4, 5, 6, 7).takeWhile(n => n != 4)
   *
   * Will result in: StreamingT[List, Int](1, 2, 3)
   */
  def takeWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] =
    this match {
      case Cons(a, ft) => if (f(a)) Cons(a, ft.map(_.takeWhile(f))) else Empty()
      case Wait(ft) => Wait(ft.map(_.takeWhile(f)))
      case Empty() => Empty()
    }

  /**
   * From the beginning of this stream, create a new stream which
   * removes all elements that fulfill the predicate `f`. Once an
   * element is found which does not fulfill the predicate, that
   * element and all subsequent elements will be returned.
   *
   * If all elements satisfy `f`, an empty stream will be returned.
   * If no elements satisfy `f`, the current stream will be returned.
   *
   * For example:
   *
   *   StreamingT[List, Int](1, 2, 3, 4, 5, 6, 7).dropWhile(n => n != 4)
   *
   * Will result in: StreamingT[List, Int](4, 5, 6, 7)
   */
  def dropWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] =
    this match {
      case Cons(a, ft) => if (f(a)) Empty() else Cons(a, ft.map(_.takeWhile(f)))
      case Wait(ft) => Wait(ft.map(_.dropWhile(f)))
      case Empty() => Empty()
    }

  /**
   * Provide a list of elements in the stream.
   *
   * This will evaluate the stream immediately, and will hang in the
   * case of infinite streams.
   */
  def toList(implicit ev: Monad[F]): F[List[A]] =
    this match {
      case Cons(a, ft) => ft.flatMap(_.toList).map(a :: _)
      case Wait(ft) => ft.flatMap(_.toList)
      case Empty() => ev.pure(Nil)
    }

  /**
   * Basic string representation of a stream.
   *
   * This method will not force evaluation of any lazy part of a
   * stream. As a result, you will see at most one element (the first
   * one).
   *
   * Use .toString(n) to see the first n elements of the stream.
   */
  override def toString: String =
    "StreamingT(...)"
}

object StreamingT extends StreamingTInstances {

  /**
   * Concrete StreamingT[A] types:
   *
   *  - Empty(): an empty stream.
   *  - Cons(a, tail): a non-empty stream containing (at least) `a`.
   *  - Wait(tail): a deferred stream.
   *
   * Cons represents a lazy, possibly infinite stream of values.
   * Eval[_] is used to represent possible laziness (via Now, Later,
   * and Always). The head of `Cons` is eager -- a lazy head can be
   * represented using `Wait(Always(...))` or `Wait(Later(...))`.
   */
  private[cats] case class Empty[F[_], A]() extends StreamingT[F, A]
  private[cats] case class Wait[F[_], A](next: F[StreamingT[F, A]]) extends StreamingT[F, A]
  private[cats] case class Cons[F[_], A](a: A, tail: F[StreamingT[F, A]]) extends StreamingT[F, A]

  /**
   * Create an empty stream of type A.
   */
  def empty[F[_], A]: StreamingT[F, A] =
    Empty()

  /**
   * Create a stream consisting of a single `A` value.
   */
  def apply[F[_], A](a: A)(implicit ev: Applicative[F]): StreamingT[F, A] =
    Cons(a, ev.pure(Empty()))

  /**
   * Create a stream from two or more values.
   */
  def apply[F[_], A](a1: A, a2: A, as: A*)(implicit ev: Applicative[F]): StreamingT[F, A] =
    Cons(a1, ev.pure(Cons(a2, ev.pure(StreamingT.fromVector[F, A](as.toVector)))))

  /**
   * Create a stream from a vector.
   */
  def fromVector[F[_], A](as: Vector[A])(implicit ev: Applicative[F]): StreamingT[F, A] = {
    def loop(s: StreamingT[F, A], i: Int): StreamingT[F, A] =
      if (i < 0) s else loop(Cons(as(i), ev.pure(s)), i - 1)
    loop(Empty(), as.length - 1)
  }

  /**
   * Create a stream from a list.
   */
  def fromList[F[_], A](as: List[A])(implicit ev: Applicative[F]): StreamingT[F, A] = {
    def loop(s: StreamingT[F, A], ras: List[A]): StreamingT[F, A] =
      ras match {
        case Nil => s
        case a :: rt => loop(Cons(a, ev.pure(s)), rt)
      }
    loop(Empty(), as.reverse)
  }

  /**
   * Create a stream consisting of a single `F[A]`.
   */
  def single[F[_]: Applicative, A](a: F[A]): StreamingT[F, A] =
    Wait(a.map(apply(_)))

  /**
   * Create a stream from `A` and `F[StreamingT[F, A]]` values.
   */
  def cons[F[_], A](a: A, fs: F[StreamingT[F, A]]): StreamingT[F, A] =
    Cons(a, fs)

  /**
   * Create a stream from an `F[StreamingT[F, A]]` value.
   */
  def defer[F[_], A](s: => StreamingT[F, A])(implicit ev: Applicative[F]): StreamingT[F, A] =
    Wait(ev.pureEval(Always(s)))

  /**
   * Create a stream from an `F[StreamingT[F, A]]` value.
   */
  def wait[F[_], A](fs: F[StreamingT[F, A]]): StreamingT[F, A] =
    Wait(fs)

  /**
   * Produce a stream given an "unfolding" function.
   *
   * None represents an empty stream. Some(a) reprsents an initial
   * element, and we can compute the tail (if any) via f(a).
   */
  def unfold[F[_]: Functor, A](o: Option[A])(f: A => F[Option[A]]): StreamingT[F, A] =
    o match {
      case Some(a) =>
        Cons(a, f(a).map(o => unfold(o)(f)))
      case None =>
        Empty()
    }

  /**
   * Contains syntax for F[Streaming[F, A]].
   *
   * To eanble this, say:
   *
   *   import cats.data.StreamingT.syntax._
   *
   * This provides the %:: and %::: operators for prepending to an
   * F[Streaming[F, A]] value, as well as a lazy Streaming[F, A]
   * value. This mirrors the methods of the same name which can be
   * used to prepend to a Streaming[F, A] value.
   *
   * In order to support laziness when using F[Streaming[F, A]]
   * values, the type constructor F[_] must support laziness, and the
   * F[Streaming[F, A]] value must be constructed lazily.
   *
   * For example, `StreamingT[Option, ?]` cannot support laziness,
   * because Option[_] is eager.
   *
   * Additionally, `x %:: Future.successful(xs)` will not produce a
   * lazy StreamT[Future, ?], since `xs` will already have been
   * evaluated.
   */
  object syntax {
    implicit class StreamingTOps[F[_], A](rhs: => StreamingT[F, A]) {
      def %::(a: A)(implicit ev: Applicative[F]): StreamingT[F, A] =
        Cons(a, ev.pureEval(Always(rhs)))
      def %:::(s: StreamingT[F, A])(implicit ev: Monad[F]): StreamingT[F, A] =
        s concat ev.pureEval(Always(rhs))
      def %::(fa: F[A])(implicit ev: Monad[F]): StreamingT[F, A] =
        Wait(fa.map(a => Cons(a, ev.pureEval(Always(rhs)))))
      def %:::(fs: F[StreamingT[F, A]])(implicit ev: Monad[F]): StreamingT[F, A] =
        Wait(fs.map(_ concat ev.pureEval(Always(rhs))))
    }

    implicit class FStreamingTOps[F[_], A](rhs: F[StreamingT[F, A]]) {
      def %::(a: A): StreamingT[F, A] =
        Cons(a, rhs)
      def %:::(s: StreamingT[F, A])(implicit ev: Monad[F]): StreamingT[F, A] =
        s concat rhs
      def %::(fa: F[A])(implicit ev: Functor[F]): StreamingT[F, A] =
        Wait(fa.map(a => Cons(a, rhs)))
      def %:::(fs: F[StreamingT[F, A]])(implicit ev: Monad[F]): StreamingT[F, A] =
        Wait(fs.map(_ concat rhs))
    }
  }
}

private[data] sealed trait StreamingTInstances extends StreamingTInstances1 {

  implicit def streamingTInstance[F[_]: Monad]: MonadCombine[StreamingT[F, ?]] with CoflatMap[StreamingT[F, ?]] =
    new MonadCombine[StreamingT[F, ?]] with CoflatMap[StreamingT[F, ?]] {
      def pure[A](a: A): StreamingT[F, A] =
        StreamingT(a)
      def flatMap[A, B](fa: StreamingT[F, A])(f: A => StreamingT[F, B]): StreamingT[F, B] =
        fa.flatMap(f)
      def empty[A]: StreamingT[F, A] =
        StreamingT.empty
      def combine[A](xs: StreamingT[F, A], ys: StreamingT[F, A]): StreamingT[F, A] =
        xs %::: ys
      override def filter[A](fa: StreamingT[F, A])(f: A => Boolean): StreamingT[F, A] =
        fa.filter(f)
      def coflatMap[A, B](fa: StreamingT[F, A])(f: StreamingT[F, A] => B): StreamingT[F, B] =
        fa.coflatMap(f)
    }

  implicit def streamingTOrder[F[_], A](implicit ev: Monad[F], eva: Order[F[List[A]]]): Order[StreamingT[F, A]] =
    new Order[StreamingT[F, A]] {
      def compare(x: StreamingT[F, A], y: StreamingT[F, A]): Int =
        x.toList compare y.toList
    }
}

private[data] sealed trait StreamingTInstances1 extends StreamingTInstances2 {
  implicit def streamingTPartialOrder[F[_], A](implicit ev: Monad[F], eva: PartialOrder[F[List[A]]]): PartialOrder[StreamingT[F, A]] =
    new PartialOrder[StreamingT[F, A]] {
      def partialCompare(x: StreamingT[F, A], y: StreamingT[F, A]): Double =
        x.toList partialCompare y.toList
    }
}

private[data] sealed trait StreamingTInstances2 {
  implicit def streamingTEq[F[_], A](implicit ev: Monad[F], eva: Eq[F[List[A]]]): Eq[StreamingT[F, A]] =
    new Eq[StreamingT[F, A]] {
      def eqv(x: StreamingT[F, A], y: StreamingT[F, A]): Boolean =
        x.toList === y.toList
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy