codata.tee.scala Maven / Gradle / Ivy
package org.specs2.codata
import Cause._
import scala.annotation.tailrec
import scalaz.{\/-, \/}
import scalaz.\/._
import org.specs2.codata.Process._
import org.specs2.codata.Util._
object tee {
/**
* Alternate pulling from the left, then right,
* repeatedly, starting on the left, and emitting only values
* from the right. When the left is exhausted, behaves like `passR`.
*/
def drainL[I]: Tee[Any, I, I] =
awaitL[Any].awaitOption.flatMap {
case None => passR
case Some(_) => awaitR[I] ++ drainL
}
/**
* Alternate pulling from the left, then right,
* repeatedly, starting on the left, and emitting only values
* from the left. When the right is exhausted, behaves like `passL`.
*/
def drainR[I]: Tee[I, Any, I] =
awaitL[I] ++ awaitR[Any].awaitOption.flatMap {
case None => passL
case Some(_) => drainR
}
/** A `Tee` which alternates between emitting values from the left input and the right input. */
def interleave[I]: Tee[I, I, I] =
repeat { for {
i1 <- awaitL[I]
i2 <- awaitR[I]
r <- emit(i1) ++ emit(i2)
} yield r }
/** A `Tee` which ignores all input from left. */
def passR[I2]: Tee[Any, I2, I2] = awaitR[I2].repeat
/** A `Tee` which ignores all input from the right. */
def passL[I]: Tee[I, Any, I] = awaitL[I].repeat
/** Echoes the right branch until the left branch becomes `true`, then halts. */
def until[I]: Tee[Boolean, I, I] =
awaitL[Boolean].flatMap(kill => if (kill) halt else awaitR[I] ++ until)
/** Echoes the right branch when the left branch is `true`. */
def when[I]: Tee[Boolean, I, I] =
awaitL[Boolean].flatMap(ok => if (ok) awaitR[I] ++ when else when)
/** Defined as `zipWith((_,_))` */
def zip[I, I2]: Tee[I, I2, (I, I2)] = zipWith((_, _))
/** Defined as `zipWith((arg,f) => f(arg)` */
def zipApply[I,I2]: Tee[I, I => I2, I2] = zipWith((arg,f) => f(arg))
/** A version of `zip` that pads the shorter stream with values. */
def zipAll[I, I2](padI: I, padI2: I2): Tee[I, I2, (I, I2)] =
zipWithAll(padI, padI2)((_, _))
/**
* Zip together two inputs, then apply the given function,
* halting as soon as either input is exhausted.
* This implementation reads from the left, then the right.
*/
def zipWith[I, I2, O](f: (I, I2) => O): Tee[I, I2, O] = {
for {
i <- awaitL[I]
i2 <- awaitR[I2]
r <- emit(f(i, i2))
} yield r
} repeat
/** A version of `zipWith` that pads the shorter stream with values. */
def zipWithAll[I, I2, O](padI: I, padI2: I2)(
f: (I, I2) => O): Tee[I, I2, O] = {
def fbR: Tee[I, I2, O] = receiveR { i2 => emit(f(padI, i2)) ++ fbR }
def fbL: Tee[I, I2, O] = receiveL { i => emit(f(i, padI2)) ++ fbL }
def go: Tee[I, I2, O] = {
receiveLOr[I, I2, O](fbR) { i =>
receiveROr[I, I2, O](emit(f(i, padI2)) ++ fbL) { i2 =>
emit(f(i, i2)) ++ go
}
}
}
go
}
/** Feed a sequence of inputs to the left side of a `Tee`. */
def feedL[I, I2, O](i: Seq[I])(p: Tee[I, I2, O]): Tee[I, I2, O] = {
@tailrec
def go(in: Seq[I], out: Vector[Seq[O]], cur: Tee[I, I2, O]): Tee[I, I2, O] = {
if (in.nonEmpty) cur.step match {
case Step(Emit(os), cont) =>
go(in, out :+ os, cont.continue)
case Step(awt@AwaitL(rcv), cont) =>
go(in.tail, out, rcv(right(in.head)) +: cont)
case Step(awt@AwaitR(rcv), cont) =>
emitAll(out.flatten) onHalt {
case End => awt.extend(p => feedL(in)(p +: cont))
case early : EarlyCause => feedL(in)(rcv(left(early)) +: cont)
}
case Halt(rsn) => emitAll(out.flatten).causedBy(rsn)
} else cur.prepend(out.flatten)
}
go(i, Vector(), p)
}
/** Feed a sequence of inputs to the right side of a `Tee`. */
def feedR[I, I2, O](i: Seq[I2])(p: Tee[I, I2, O]): Tee[I, I2, O] = {
@tailrec
def go(in: Seq[I2], out: Vector[Seq[O]], cur: Tee[I, I2, O]): Tee[I, I2, O] = {
if (in.nonEmpty) cur.step match {
case Step(Emit(os),cont) =>
go(in, out :+ os, cont.continue)
case Step(awt@AwaitL(rcv), cont) =>
emitAll(out.flatten) onHalt {
case End => awt.extend(p => feedR(in)(p +: cont))
case early : EarlyCause => feedR(in)(rcv(left(early)) +: cont)
}
case Step(awt@AwaitR(rcv), cont) =>
go(in.tail, out, rcv(right(in.head)) +: cont)
case Halt(rsn) => emitAll(out.flatten).causedBy(rsn)
} else cur.prepend(out.flatten)
}
go(i, Vector(), p)
}
/** Feed one input to the left branch of this `Tee`. */
def feed1L[I, I2, O](i: I)(t: Tee[I, I2, O]): Tee[I, I2, O] =
feedL(Vector(i))(t)
/** Feed one input to the right branch of this `Tee`. */
def feed1R[I, I2, O](i2: I2)(t: Tee[I, I2, O]): Tee[I, I2, O] =
feedR(Vector(i2))(t)
/**
* Signals, that _left_ side of tee terminated.
* That causes all succeeding AwaitL to terminate with `cause` giving chance
* to emit any values or read on right.
*/
def disconnectL[I, I2, O](cause: EarlyCause)(tee: Tee[I, I2, O]): Tee[Nothing, I2, O] = {
tee.step match {
case Step(emt@Emit(_), cont) => emt +: cont.extend(disconnectL(cause)(_))
case Step(AwaitL(rcv), cont) => suspend(disconnectL(cause)(rcv(left(cause)) +: cont))
case Step(awt@AwaitR(rcv), cont) => awt.extend(p => disconnectL[I,I2,O](cause)(p +: cont))
case hlt@Halt(rsn) => Halt(rsn)
}
}
/**
* Signals, that _right_ side of tee terminated.
* That causes all succeeding AwaitR to terminate with `cause` giving chance
* to emit any values or read on left.
*/
def disconnectR[I, I2, O](cause: EarlyCause)(tee: Tee[I, I2, O]): Tee[I, Nothing, O] = {
tee.step match {
case Step(emt@Emit(os), cont) => emt +: cont.extend(disconnectR(cause)(_))
case Step(AwaitR(rcv), cont) => suspend(disconnectR(cause)(rcv(left(cause)) +: cont))
case Step(awt@AwaitL(rcv), cont) => awt.extend(p => disconnectR[I,I2,O](cause)(p +: cont))
case Halt(rsn) => Halt(rsn)
}
}
/**
* Awaits to receive input from Left side,
* than if that request terminates with `End` or is terminated abnormally
* runs the supplied `continue` or `cleanup`.
* Otherwise `rcv` is run to produce next state.
*
* If you don't need `continue` or `cleanup` use rather `awaitL.flatMap`
*/
def receiveL[I, I2, O](rcv: I => Tee[I, I2, O]): Tee[I, I2, O] =
await[Env[I, I2]#T, I, O](L)(rcv)
/**
* Awaits to receive input from Right side,
* than if that request terminates with `End` or is terminated abnormally
* runs the supplied continue.
* Otherwise `rcv` is run to produce next state.
*
* If you don't need `continue` or `cleanup` use rather `awaitR.flatMap`
*/
def receiveR[I, I2, O](rcv: I2 => Tee[I, I2, O]): Tee[I, I2, O] =
await[Env[I, I2]#T, I2, O](R)(rcv)
/** syntax sugar for receiveL */
def receiveLOr[I, I2, O](fb: => Tee[I, I2, O])(rcvL: I => Tee[I, I2, O]): Tee[I, I2, O] =
awaitOr[Env[I, I2]#T, I, O](L)(rsn => fb.causedBy(rsn))(rcvL)
/** syntax sugar for receiveR */
def receiveROr[I, I2, O](fb: => Tee[I, I2, O])(rcvR: I2 => Tee[I, I2, O]): Tee[I, I2, O] =
awaitOr[Env[I, I2]#T, I2, O](R)(rsn => fb.causedBy(rsn))(rcvR)
//////////////////////////////////////////////////////////////////////
// De-constructors
//////////////////////////////////////////////////////////////////////
type TeeAwaitL[I, I2, O] = Await[Env[I, I2]#T, Env[I, Any]#Is[I], O]
type TeeAwaitR[I, I2, O] = Await[Env[I, I2]#T, Env[Any, I2]#T[I2], O]
object AwaitL {
def unapply[I, I2, O](self: TeeAwaitL[I, I2, O]):
Option[(EarlyCause \/ I => Tee[I, I2, O])] = self match {
case Await(req, rcv,_)
if req.tag == 0 => Some((r: EarlyCause \/ I) =>
Try(rcv.asInstanceOf[(EarlyCause \/ I) => Trampoline[Tee[I,I2,O]]](r).run))
case _ => None
}
/** Like `AwaitL.unapply` only allows fast test that wye is awaiting on left side */
object is {
def unapply[I, I2, O](self: TeeAwaitL[I, I2, O]): Boolean = self match {
case Await(req, rcv,_) if req.tag == 0 => true
case _ => false
}
}
}
object AwaitR {
def unapply[I, I2, O](self: TeeAwaitR[I, I2, O]):
Option[(EarlyCause \/ I2 => Tee[I, I2, O])] = self match {
case Await(req, rcv,_)
if req.tag == 1 => Some((r: EarlyCause \/ I2) =>
Try(rcv.asInstanceOf[(EarlyCause \/ I2) => Trampoline[Tee[I,I2,O]]](r).run))
case _ => None
}
/** Like `AwaitR.unapply` only allows fast test that wye is awaiting on left side */
object is {
def unapply[I, I2, O](self: TeeAwaitR[I, I2, O]): Boolean = self match {
case Await(req, rcv,_) if req.tag == 1 => true
case _ => false
}
}
}
}
/**
* Operations on process that uses `tee`
*/
private[codata] trait TeeOps[+F[_], +O] {
self: Process[F, O] =>
/** Alternate emitting elements from `this` and `p2`, starting with `this`. */
def interleave[F2[x] >: F[x], O2 >: O](p2: Process[F2, O2]): Process[F2, O2] =
this.tee(p2)(org.specs2.codata.tee.interleave[O2])
/** Call `tee` with the `zipWith` `Tee[O,O2,O3]` defined in `tee.scala`. */
def zipWith[F2[x] >: F[x], O2, O3](p2: Process[F2, O2])(f: (O, O2) => O3): Process[F2, O3] =
this.tee(p2)(org.specs2.codata.tee.zipWith(f))
/** Call `tee` with the `zip` `Tee[O,O2,O3]` defined in `tee.scala`. */
def zip[F2[x] >: F[x], O2](p2: Process[F2, O2]): Process[F2, (O, O2)] =
this.tee(p2)(org.specs2.codata.tee.zip)
/**
* When `condition` is `true`, lets through any values in `this` process, otherwise blocks
* until `condition` becomes true again. Note that the `condition` is checked before
* each and every read from `this`, so `condition` should return very quickly or be
* continuous to avoid holding up the output `Process`. Use `condition.forwardFill` to
* convert an infrequent discrete `Process` to a continuous one for use with this
* function.
*/
def when[F2[x] >: F[x], O2 >: O](condition: Process[F2, Boolean]): Process[F2, O2] =
condition.tee(this)(org.specs2.codata.tee.when)
/** Delay running this `Process` until `awaken` becomes true for the first time. */
def sleepUntil[F2[x] >: F[x], O2 >: O](awaken: Process[F2, Boolean]): Process[F2, O2] =
Process.sleepUntil(awaken)(this)
/**
* Halts this `Process` as soon as `condition` becomes `true`. Note that `condition`
* is checked before each and every read from `this`, so `condition` should return
* very quickly or be continuous to avoid holding up the output `Process`. Use
* `condition.forwardFill` to convert an infrequent discrete `Process` to a
* continuous one for use with this function.
*/
def until[F2[x] >: F[x], O2 >: O](condition: Process[F2, Boolean]): Process[F2, O2] =
condition.tee(this)(org.specs2.codata.tee.until)
}
/**
* This class provides infix syntax specific to `Tee`. We put these here
* rather than trying to cram them into `Process` itself using implicit
* equality witnesses. This doesn't work out so well due to variance
* issues.
*/
final class TeeSyntax[I, I2, O](val self: Tee[I, I2, O]) extends AnyVal {
/** Transform the left input to a `Tee`. */
def contramapL[I0](f: I0 => I): Tee[I0, I2, O] =
self.contramapL_(f).asInstanceOf[Tee[I0, I2, O]]
/** Transform the right input to a `Tee`. */
def contramapR[I3](f: I3 => I2): Tee[I, I3, O] =
self.contramapR_(f).asInstanceOf[Tee[I, I3, O]]
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy