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

scavenger.Algorithm.scala Maven / Gradle / Ivy

The newest version!
package scavenger

import scala.concurrent.Future
import scala.concurrent.ExecutionContext
import scavenger.categories.formalccc

/** Arbitrarily complex composition (including pairs, projections, currying) of atomic algorithms.
  *
  * The most general method to transform a `Computation[X]` into `Computation[Y]`.
  *
  * @since 2.1
  * @author AndreyTyukin
  */
trait Algorithm[-X, +Y] extends Serializable{ outer =>
  def identifier: formalccc.Elem

  /** Creates a new computation modified by this algorithm
    */
  def apply(computation: Computation[X]): Computation[Y] 

  /** Determines how every output of this algorithm is cached.
    */
  def cachingPolicy: CachingPolicy = CachingPolicy.Nowhere

  /** Composes with another algorithm (Order: this=first, other=second)
    */
  def andThen[Z](other: Algorithm[Y, Z]): Algorithm[X, Z] = 
  new Algorithm[X, Z] {
    def identifier = other.identifier o outer.identifier
    def apply(computation: Computation[X]) = other(outer(computation))
  }

  /** Composes with another algorithm (Order: this=second, other=first)
    */
  def o[W](other: Algorithm[W, X]): Algorithm[W, Y] = other andThen this

  /** Creates pair of two arrows with same domain
    */
  def zip[D <: X, Z](other: Algorithm[D, Z]): Algorithm[D, (Y, Z)] = 
    AlgorithmPair[D, Y, Z](this, other)

  /** Glues two parallel arrows into one
    */
  def cross[A, B](other: Algorithm[A, B]): Algorithm[(X, A), (Y, B)] = 
    (this o Fst[X, A]) zip (other o Snd[X, A])

  /* Doesn't work: variance doesn't work out.
  def curryFst[A, B](a: Computation[A])(ccf: CanCurryFst[X, A, B, Y]):
    Algorithm[B, Y] = ccf(this, a)

  def currySnd[A, B](b: Computation[B])(ccs: CanCurrySnd[X, A, B, Y]):
    Algorithm[A, Y] = ccs(this, b)
  */

  def curryFst[A, B](a: Computation[A])(implicit cbp: CanBuildProduct[A, B, X]):
  Algorithm[B, Y] =
  new Algorithm[B, Y] {
    def identifier = formalccc.Curry(outer.identifier)(a.identifier)
    def apply(b: Computation[B]) = outer(cbp(a, b))
  }

  def currySnd[A, B](b: Computation[B])(implicit cbp: CanBuildProduct[A, B, X]):
  Algorithm[A, Y] =
  new Algorithm[A, Y] {
    import formalccc.{Curry => FCurry, _}
    // this one is a little tricky, see p. 61 third black CS book
    def identifier = FCurry(outer.identifier o Pair(Snd, Fst))(b.identifier)
    def apply(a: Computation[A]) = outer(cbp(a, b))
  }

  /** Returns a an that looks exactly the same, except that the
    * output of this algorithm has a different caching policy.
    */
  private[scavenger] def withCachingPolicy(newCachingPolicy: CachingPolicy): 
  Algorithm[X, Y] = 
    new Algorithm[X, Y] {
      def identifier = outer.identifier
      override def cachingPolicy = newCachingPolicy
      def apply(computation: Computation[X]): Computation[Y] = 
        outer.apply(computation).withCachingPolicy(newCachingPolicy)
    }

  /** Creates new computation that does exactly the same, but is additionally
    * cached on the Master node.
    */
  def cacheGlobally = withCachingPolicy(cachingPolicy.copy(cacheGlobally=true))
 
  /** Creates new computation that does exactly the same, but is additionally
    * cached on the worker nodes.
    */
  def cacheLocally = withCachingPolicy(cachingPolicy.copy(cacheLocally = true)) 

  /** Instructs the master node to back up the result of this computation
    */
  def backUp = withCachingPolicy(cachingPolicy.copy(backup = true))
}

/** `CanCurryFst` is an implicitly generated argument that
  * ensures that we can curry only methods that actually take
  * product types as inputs.
  *
  * Implementations are provided in the `package.scala` file.
  */
/*
trait CanCurryFst[+In, -InA, -InB, +Out] {
  def apply(
    f: Algorithm[In, Out],
    input: Computation[InA]
  ): Algorithm[InB, Out]
}

trait CanCurrySnd[+In, -InA, -InB, +Out] {
  def apply(
    f: Algorithm[In, Out],
    input: Computation[InB]
  ): Algorithm[InA, Out]
}
*/

trait CanBuildProduct[-A, -B, +Prod] {
  def apply(a: Computation[A], b: Computation[B]): Computation[Prod]
}

/** This is the natural notion of a morphism between
  * objects of type `Computation[X]` for some `X`.
  *
  * An `AtomicAlgorithm[X,Y]` describes how to obtain a value
  * of type `Y` from a value of type `X` using a
  * context. The computation can in general take some time,
  * therefore the return type of the `apply` method is
  * `Future[Y]`.
  *
  * Furthermore, an algorithm provides a symbolic
  * identifier, that enables us to identify modified
  * computations, and load them from cache or a file, if
  * possible.
  */
abstract class AtomicAlgorithm[-X, +Y] 
extends Algorithm[X, Y] 
with ((X, Context) => Future[Y]) { outer => 
  def difficulty: Difficulty
  def apply(x: X, ctx: Context): Future[Y]

  def apply(computation: Computation[X]): Computation[Y] = {
    computation.flatMap(identifier, difficulty)(this)
  }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy