
com.twitter.util.Activity.scala Maven / Gradle / Ivy
The newest version!
package com.twitter.util
import scala.collection.generic.CanBuild
/**
* An Activity is a handle to a concurrently running process, producing
* T-typed values. An activity is in one of three states:
*
* - [[com.twitter.util.Activity.Pending Pending]]: output is pending;
* - [[com.twitter.util.Activity.Ok Ok]]: an output is available; and
* - [[com.twitter.util.Activity.Failed Failed]]: the process failed with an exception.
*
* An activity may transition between any state at any time.
*
* (The observant reader will notice that this is really a monad
* transformer for an ''Op'' monad over [[com.twitter.util.Var Var]]
* where Op is like a [[com.twitter.util.Try Try]] with an additional
* pending state.)
*/
case class Activity[+T](run: Var[Activity.State[T]]) {
import Activity._
/**
* Map a T-typed activity to a U-typed one.
*/
def map[U](f: T => U): Activity[U] = collect { case x => f(x) }
/**
* Build a new activity by applying `f` to each value. When
* `f` is not defined for this activity's current value, the derived
* activity becomes pending.
*/
def collect[U](f: PartialFunction[T, U]): Activity[U] = flatMap {
case t if f.isDefinedAt(t) =>
try Activity.value(f(t)) catch {
case NonFatal(exc) => Activity.exception(exc)
}
case _ => Activity.pending
}
/**
* Join two activities.
*/
def join[U](that: Activity[U]): Activity[(T, U)] =
for (left <- this; right <- that) yield (left, right)
/**
* The activity which behaves as `f` applied to Ok values.
*/
def flatMap[U](f: T => Activity[U]): Activity[U] =
Activity(run flatMap {
case Ok(v) =>
val a = try f(v) catch {
case NonFatal(exc) => Activity.exception(exc)
}
a.run
case Pending => Var.value(Activity.Pending)
case exc@Failed(_) => Var.value(exc)
})
/**
* The activity which behaves as `f` to the state
* of this activity.
*/
def transform[U](f: Activity.State[T] => Activity[U]): Activity[U] =
Activity(run flatMap { act =>
val a = try f(act) catch {
case NonFatal(exc) => Activity.exception(exc)
}
a.run
})
/**
* An [[com.twitter.util.Event Event]] of states.
*/
def states: Event[State[T]] = run.changes
/**
* An [[com.twitter.util.Event Event]] containing only nonpending
* values.
*/
def values: Event[Try[T]] = states collect {
case Ok(v) => Return(v)
case Failed(exc) => Throw(exc)
}
/**
* Sample the current value of this activity. Sample throws an
* exception if the activity is in pending state or has failed.
*/
def sample(): T = Activity.sample(this)
}
object Activity {
/**
* Create a new pending activity. The activity's state is updated by
* the given witness.
*/
def apply[T](): (Activity[T], Witness[Try[T]]) = {
val v = Var(Pending: State[T])
val w: Witness[Try[T]] = Witness(v) comap {
case Return(v) => Ok(v)
case Throw(exc) => Failed(exc)
}
(Activity(v), w)
}
/**
* Collect a collection of activities into an activity of a collection
* of values.
*
* @usecase def collect[T](activities: Coll[Activity[T]]): Activity[Coll[T]]
*
* @inheritdoc
*/
def collect[T, CC[X] <: Traversable[X]](acts: CC[Activity[T]])
(implicit newBuilder: CanBuild[T, CC[T]], cm: ClassManifest[T])
: Activity[CC[T]] = {
if (acts.isEmpty)
return Activity.value(newBuilder().result)
val states: Traversable[Var[State[T]]] = acts.map(_.run)
val stateVar: Var[Traversable[State[T]]] = Var.collect(states)
def flip(states: Traversable[State[T]]): State[CC[T]] = {
val notOk = states find {
case Pending | Failed(_) => true
case Ok(_) => false
}
notOk match {
case None =>
case Some(Pending) => return Pending
case Some(f@Failed(_)) => return f
case Some(_) => assert(false)
}
val ts = newBuilder()
states foreach {
case Ok(t) => ts += t
case _ => assert(false)
}
Ok(ts.result)
}
Activity(stateVar map flip)
}
def sample[T](act: Activity[T]): T =
act.run.sample() match {
case Ok(t) => t
case Pending => throw new IllegalStateException("Still pending")
case Failed(exc) => throw exc
}
/**
* Create a new static activity with value `v`.
*/
def value[T](v: T): Activity[T] = Activity(Var.value(Ok(v)))
/**
* Create a new static activity with exception `exc`.
*/
def exception(exc: Throwable): Activity[Nothing] = Activity(Var.value(Failed(exc)))
/**
* A static Activity that is pending.
*/
val pending: Activity[Nothing] = Activity(Var.value(Pending))
/**
* An ADT describing the state of an Activity.
*/
sealed trait State[+T]
/**
* The activity is running with a current value of `t`.
*/
case class Ok[T](t: T) extends State[T]
/**
* The activity is pending output.
*/
object Pending extends State[Nothing]
/**
* The activity has failed, with exception `exc`.
*/
case class Failed(exc: Throwable) extends State[Nothing]
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy