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

zio.concurrent.MVar.scala Maven / Gradle / Ivy

There is a newer version: 2.1.13
Show newest version
package zio.concurrent

import zio.stm.TRef
import zio._
import zio.stm.STM

/**
 * An `MVar[A]` is a mutable location that is either empty or contains a value
 * of type `A`. It has two fundamental operations: `put` which fills an `MVar`
 * if it is empty and blocks otherwise, and `take` which empties an `MVar` if it
 * is full and blocks otherwise. They can be used in multiple different ways:
 *
 *   - As synchronized mutable variables,
 *   - As channels, with `take` and `put` as `receive` and `send`, and
 *   - As a binary semaphore `MVar[Unit]`, with `take` and `put` as `wait` and
 *     `signal`.
 *
 * They were introduced in the paper "Concurrent Haskell" by Simon Peyton Jones,
 * Andrew Gordon and Sigbjorn Finne.
 */
final class MVar[A] private (private val content: TRef[Option[A]]) {

  /**
   * Check whether the `MVar` is empty.
   *
   * Notice that the boolean value returned is just a snapshot of the state of
   * the `MVar`. By the time you get to react on its result, the `MVar` may have
   * been filled (or emptied) - so be extremely careful when using this
   * operation. Use `tryTake` instead if possible.
   */
  def isEmpty: UIO[Boolean] =
    content.get.map(_.isEmpty).commit

  /**
   * A slight variation on `update` that allows a value to be returned (`b`) in
   * addition to the modified value of the `MVar`.
   */
  def modify[B](f: A => (B, A)): UIO[B] =
    content.get.collect { case Some(a) =>
      val (b, newA) = f(a)
      (b, Some(newA))
    }.flatMap { case (b, newA) => content.set(newA) as b }.commit

  /**
   * Put a value into an `MVar`. If the `MVar` is currently full, `put` will
   * wait until it becomes empty.
   */
  def put(x: A): UIO[Unit] =
    (content.get.collect { case None => () } *> content.set(Some(x))).commit

  /**
   * Atomically read the contents of an `MVar`. If the `MVar` is currently
   * empty, `read` will wait until it is full. `read` is guaranteed to receive
   * the next `put`.
   */
  def read: UIO[A] =
    content.get.collect { case Some(x) => x }.commit

  /**
   * Take a value from an `MVar`, put a new value into the `MVar` and return the
   * value taken.
   */
  def swap(x: A): UIO[A] =
    (for {
      ref <- content.get
      y <- ref match {
             case Some(y) => content.set(Some(x)) as y
             case None    => STM.retry
           }
    } yield y).commit

  /**
   * Return the contents of the `MVar`. If the `MVar` is currently empty, `take`
   * will wait until it is full. After a `take`, the `MVar` is left empty.
   */
  def take: UIO[A] =
    (content.get.collect { case Some(a) => a }.flatMap(a => content.set(None) as a)).commit

  /**
   * A non-blocking version of `put`. The `tryPut` function attempts to put the
   * value `x` into the `MVar`, returning `true` if it was successful, or
   * `false` otherwise.
   */
  def tryPut(x: A): UIO[Boolean] =
    (content.get.flatMap {
      case _: Some[_] => STM.succeed(false)
      case None       => content.set(Some(x)) as true
    }).commit

  /**
   * A non-blocking version of `read`. The `tryRead` function returns
   * immediately, with `None` if the `MVar` was empty, or `Some(x)` if the
   * `MVar` was full with contents `x`.
   */
  def tryRead: UIO[Option[A]] =
    content.get.commit

  /**
   * A non-blocking version of `take`. The `tryTake` action returns immediately,
   * with `None` if the `MVar` was empty, or `Some(x)` if the `MVar` was full
   * with contents `x`. After `tryTake`, the `MVar` is left empty.
   */
  def tryTake: UIO[Option[A]] =
    (for {
      c <- content.get
      a <- c match {
             case Some(a) => content.set(None) *> STM.succeed(Some(a))
             case None    => STM.succeed(None)
           }
    } yield a).commit

  /**
   * Replaces the contents of an `MVar` with the result of `f(a)`.
   */
  def update(f: A => A): UIO[Unit] =
    content.get.collect { case Some(a) => Some(f(a)) }.flatMap(content.set).commit
}

object MVar {

  /** Creates an `MVar` which is initially empty */
  def empty[A]: UIO[MVar[A]] =
    TRef.make(Option.empty[A]).map(new MVar(_)).commit

  /** Create an MVar which contains the supplied value */
  def make[A](a: A): UIO[MVar[A]] =
    TRef.make(Option(a)).map(new MVar(_)).commit
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy