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

scalaz.stream.Exchange.scala Maven / Gradle / Ivy

The newest version!
package scalaz.stream

import scalaz.\/._
import scalaz._
import scalaz.concurrent.{Strategy, Task}
import scalaz.stream.Process._
import scalaz.stream.ReceiveY.{HaltL, HaltR, ReceiveL, ReceiveR}
import scalaz.stream.wye.Request


/**
 * Exchange represents interconnection between two systems.
 * So called remote is resource from which program receives messages of type `I`
 * and can send to it messages of type `O`.
 *
 * Typically this can be sort of connection to external system,
 * like for example tcp connection to internet server.
 *
 * Exchange allows combining this pattern with Processes and
 * allows to use different combinators to specify the Exchange behaviour.
 *
 * Exchange is currently specialized to `scalaz.concurrent.Task`
 *
 * @param read Process reading values from remote system
 * @param write Process writing values to remote system
 *
 * @tparam I  values read from remote system
 * @tparam W  values written to remote system
 */
final case class Exchange[I, W](read: Process[Task, I], write: Sink[Task, W]) {

  self =>

  //alphabetical order from now on

  /**
   * uses provided function `f` to be applied on any `I` received
   */
  def mapO[I2](f: I => I2): Exchange[I2, W] =
    Exchange(self.read map f, self.write)

  /**
   * applies provided function to any `W2` that has to be written to provide an `W`
   */
  def mapW[W2](f: W2 => W): Exchange[I, W2] =
    Exchange(self.read, self.write contramap f)


  /**
   * Creates exchange that `pipe` read `I` values through supplied p1.
   * @param p1   Process1 to be used when reading values
   */
  def pipeO[I2](p1: Process1[I, I2]): Exchange[I2, W] =
    Exchange(self.read.pipe(p1), self.write)


  /**
   * Creates new exchange, that pipes all values to be sent through supplied `p1`
   */
  def pipeW[W2](p1: Process1[W2, W]): Exchange[I, W2] =
    Exchange(self.read, self.write.pipeIn(p1))

  /**
   * Creates new exchange, that will pipe read values through supplied `r` and write values through `w`
   * @param r  Process1 to use on all read values
   * @param w  Process1 to use on all written values
   */
  def pipeBoth[I2, W2](r: Process1[I, I2], w: Process1[W2, W]): Exchange[I2, W2] =
    self.pipeO(r).pipeW(w)


  /**
   * Runs supplied Process of `W` values by sending them to remote system.
   * Any replies from remote system are received as `I` values of the resulting process.
   *
   * Please note this will terminate by default after Left side (receive) terminates.
   * If you want to terminate after Right side (W) terminates, supply terminateOn with `Request.R` or `Request.Both` to
   * terminate on Right or Any side respectively
   *
   * @param p Process of `W` values to send
   * @param terminateOn Terminate on Left side (receive), Right side (W) or Any side terminates
   */
  def run(p: Process[Task, W] = halt, terminateOn: Request = Request.L)(implicit S: Strategy): Process[Task, I] = {
    import scalaz.stream.wye. {mergeHaltL, mergeHaltR, mergeHaltBoth}
    val y = terminateOn match {
      case Request.L => mergeHaltL[I]
      case Request.R => mergeHaltR[I]
      case Request.Both => mergeHaltBoth[I]
    }
    self.read.wye((p to self.write).drain)(y)
  }


  /**
   * Creates Exchange that runs read `I` through supplied effect channel.
   * @param ch Channel producing process of `I2` for each `I` received
   */
  def through[I2](ch: Channel[Task, I, Process[Task, I2]]): Exchange[I2, W] =
    Exchange((self.read through ch) flatMap identity, self.write)

  /**
   * Transform this Exchange to another Exchange where queueing, and transformation of this `I` and `W`
   * is controlled by supplied WyeW.
   *
   * Please note the `W` queue of values to be sent to server is unbounded any may cause excessive heap usage, if the
   * remote system will read `W` too slow. If you want to control this flow, use rather `flow`.
   *
   * @param y WyeW to control queueing and transformation
   *
   */
  def wye[I2,W2](y: WyeW[W, I, W2, I2])(implicit S: Strategy): Exchange[I2, W2] =
   flow(scalaz.stream.wye.attachL(process1.collect[Int \/ I,I] { case \/-(i) => i })(y))

  /**
   * Transform this Exchange to another Exchange where queueing, flow control and transformation of this `I` and `W`
   * is controlled by supplied WyeW.
   *
   * Note this allows for fine-grained flow-control of written `W` to server based on Int passed to left side of
   * supplied WyeW that contains actual size of queued values to be written to server.
   *
   *
   * @param y WyeW to control queueing, flow control and transformation
   */
  def flow[I2,W2](y: WyeW[W, Int \/ I, W2, I2])(implicit S: Strategy): Exchange[I2, W2] = {
    val wq = async.unboundedQueue[W]
    val w2q = async.unboundedQueue[W2]

    def cleanup: Process[Task, Nothing] = eval_(wq.close) ++ eval_(w2q.close)
    def receive: Process[Task, I] = self.read onComplete cleanup
    def send: Process[Task, Unit] = wq.dequeue to self.write

    def sendAndReceive = {
      val (o, ny) = y.unemit
      (emitAll(o) ++ ((wq.size.discrete either receive).wye(w2q.dequeue)(ny)(S) onComplete cleanup) either send).flatMap {
        case \/-(o) => halt
        case -\/(-\/(o)) => eval_(wq.enqueueOne(o))
        case -\/(\/-(b)) => emit(b)
      }
    }

    Exchange(sendAndReceive, w2q.enqueue)
  }

  /**
   * Transforms this exchange to another exchange, that for every received `I` will consult supplied Writer1
   * and eventually transforms `I` to `I2` or to `W` that is sent to remote system.
   *
   *
   * Please note that if external system is slow on reading `W` this can lead to excessive heap usage. If you
   * want to avoid for this to happen, please use `flow` instead.
   *
   * @param w  Writer that processes received `I` and either echoes `I2` or writes `W` to external system
   *
   */
  def readThrough[I2](w: Writer1[W, I, I2])(implicit S: Strategy) : Exchange[I2,W]  = {
    def liftWriter : WyeW[W, Int \/ I, W, I2] = {
      def go(cur:Writer1[W, I, I2]):WyeW[W, Int \/ I, W, I2] = {
        awaitBoth[Int\/I,W].flatMap{
          case ReceiveL(-\/(_)) => go(cur)
          case ReceiveL(\/-(i)) =>
            cur.feed1(i).unemit match {
              case (out,hlt@Halt(rsn)) => emitAll(out) ++ hlt
              case (out,next) => emitAll(out) ++ go(next)
            }
          case ReceiveR(w) => tell(w) ++ go(cur)
          case HaltL(rsn) => Halt(rsn)
          case HaltR(rsn) => go(cur)
        }
      }
      go(w)
    }

    self.flow[I2,W](liftWriter)(S)
  }

}

object Exchange {

  /**
   * Provides `loopBack` exchange, that loops data written to it back to `read` side, via supplied Process1.
   *
   * If process1 starts with emitting some data, they are `read` first.
   *
   * This exchange will terminate `read` side once the `p` terminates.
   *
   * Note that the `write` side is run off the thread that actually writes the messages, forcing the `read` side
   * to be run on different thread.
   *
   * This primitive may be used also for asynchronous processing that needs to be forked to different thread.
   *
   * @param p   Process to consult when looping data through
   */
  def loopBack[I, W](p: Process1[W, I])(implicit S: Strategy): Process[Task, Exchange[I, W]] = {

    def loop(cur: Process1[W, I]): WyeW[Nothing, Nothing, W, I] = {
      awaitR[W] flatMap {
        case w => cur.feed1(w).unemit match {
          case (o, hlt@Halt(rsn)) => emitAll(o.map(right)) ++ hlt
          case (o, np)            => emitAll(o.map(right)) ++ loop(np)
        }
      }
    }

    await(Task.delay {
      async.unboundedQueue[W]
    })({ q =>
      val (out, np) = p.unemit
      val ex = Exchange[Nothing, W](halt, q.enqueue)
      emit(ex.wye(emitAll(out).map(right) ++ loop(np))) onComplete eval_(q.close)
    })

  }


  /**
   * Exchange that is always halted
   */
  def halted[I,W]: Exchange[I,W] = Exchange(halt,halt)

}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy