task system cleanup

KList.map -> transform
can now drop trailing 'H' from multi-Task 'mapH'
compressed Action hierarchy by merging (Flat)Map{ped,All,Failure} into (Flat)Mapped
moved most information in Info into attributes: AttributeMap to allow future changes
This commit is contained in:
Mark Harrah 2010-08-21 22:55:42 -04:00
parent 1949fe903c
commit 368bdd2701
9 changed files with 142 additions and 82 deletions

View File

@ -207,7 +207,7 @@ final class Execute[A[_] <: AnyRef](checkCycles: Boolean)(implicit view: NodeVie
def submit[T]( node: A[T] )(implicit strategy: Strategy)
{
val v = viewCache(node)
val rs = v.mixedIn.map(results)
val rs = v.mixedIn transform results
val ud = v.uniformIn.map(results.apply[v.Uniform])
strategy.submit( node, () => work(node, v.work(rs, ud)) )
}

View File

@ -10,19 +10,11 @@ import Task._
// Various natural transformations used, such as PMap, require invariant type constructors for correctness
sealed trait Action[T]
sealed case class Pure[T](f: () => T) extends Action[T]
final case class Pure[T](f: () => T) extends Action[T]
final case class Mapped[T, In <: HList](in: Tasks[In], f: Results[In] => T) extends Action[T]
final case class MapAll[T, In <: HList](in: Tasks[In], f: In => T) extends Action[T]
final case class MapFailure[T, In <: HList](in: Tasks[In], f: Seq[Incomplete] => T) extends Action[T]
final case class FlatMapAll[T, In <: HList](in: Tasks[In], f: In => Task[T]) extends Action[T]
final case class FlatMapFailure[T, In <: HList](in: Tasks[In], f: Seq[Incomplete] => Task[T]) extends Action[T]
final case class FlatMapped[T, In <: HList](in: Tasks[In], f: Results[In] => Task[T]) extends Action[T]
final case class DependsOn[T](in: Task[T], deps: Seq[Task[_]]) extends Action[T]
final case class Join[T, U](in: Seq[Task[U]], f: Seq[U] => Either[Task[T], T]) extends Action[T]
final case class Join[T, U](in: Seq[Task[U]], f: Seq[Result[U]] => Either[Task[T], T]) extends Action[T]
object Task
{
@ -35,7 +27,22 @@ final case class Task[T](info: Info[T], work: Action[T])
def original = info.original getOrElse this
}
/** `original` is used during transformation only.*/
final case class Info[T](name: Option[String] = None, description: Option[String] = None, implied: Boolean = false, original: Option[Task[T]] = None)
final case class Info[T](attributes: AttributeMap = AttributeMap.empty, original: Option[Task[T]] = None)
{
assert(name forall (_ != null))
import Info._
def name = attributes.get(Name)
def description = attributes.get(Description)
def implied = attributes.get(Implied).getOrElse(false)
def setName(n: String) = set(Name, n)
def setDescription(d: String) = set(Description, d)
def setImplied(i: Boolean) = set(Implied, i)
def set[T](key: AttributeKey[T], value: T) = copy(attributes = this.attributes.put(key, value))
}
object Info
{
val Name = AttributeKey.make[String]
val Description = AttributeKey.make[String]
val Implied = AttributeKey.make[Boolean]
val Cross = AttributeKey.make[AttributeMap]
}

View File

@ -111,5 +111,5 @@ object Streams
}
def name(a: Task[_]): String = a.info.name getOrElse anonName(a)
def anonName(a: Task[_]) = "anon" + java.lang.Integer.toString(java.lang.System.identityHashCode(a), 36)
def anonName(a: Task[_]) = "anon-" + java.lang.Integer.toString(java.lang.System.identityHashCode(a), 36)
}

View File

@ -46,7 +46,10 @@ object System
new (Task ~> Task) {
def apply[T](in: Task[T]): Task[T] = {
val finalName = in.info.name orElse staticName(in.original)
in.copy(info = in.info.copy(name = finalName) )
finalName match {
case None => in
case Some(finalName) => in.copy(info = in.info.setName(finalName) )
}
}
}
@ -61,13 +64,12 @@ object System
* For example, this does not include the inputs of MapFailure or the dependencies of DependsOn. */
def usedInputs(t: Action[_]): Seq[Task[_]] = t match {
case m: Mapped[_,_] => m.in.toList
case m: MapAll[_,_] => m.in.toList
case m: FlatMapped[_,_] => m.in.toList
case m: FlatMapAll[_,_] => m.in.toList
case j: Join[_,_] => j.in
case _ => Nil
}
def streamed(streams: Streams, dummy: Task[TaskStreams]): Task ~> Task =
new (Task ~> Task) {
def apply[T](t: Task[T]): Task[T] = if(usedInputs(t.work) contains dummy) substitute(t) else t
@ -86,10 +88,8 @@ object System
def newWork(a: Action[T]): Task[T] = t.copy(work = a)
t.work match {
case m: Mapped[_,_] => newWork( m.copy(in = m.in map depMap, f = wrap0(m.f) ) ) // the Streams instance is valid only within the mapping function
case m: MapAll[_,_] => newWork( m.copy(in = m.in map depMap, f = wrap0(m.f) ) )
case fm: FlatMapped[_,_] => fin(newWork( fm.copy(in = fm.in map depMap) )) // the Streams instance is valid until a result is produced for the task
case fm: FlatMapAll[_,_] => fin(newWork( fm.copy(in = fm.in map depMap) ))
case m: Mapped[_,_] => newWork( m.copy(in = m.in transform depMap, f = wrap0(m.f) ) ) // the Streams instance is valid only within the mapping function
case fm: FlatMapped[_,_] => fin(newWork( fm.copy(in = fm.in transform depMap) )) // the Streams instance is valid until a result is produced for the task
case j: Join[_,u] => newWork( j.copy(j.in map depMap.fn[u], f = wrap0(j.f)) )
case _ => t // can't get a TaskStreams value from the other types, so no need to transform here (shouldn't get here anyway because of usedInputs check)
}
@ -108,6 +108,17 @@ object Transform
def subs: Owner => Iterable[Owner]
def static: (Owner, String) => Option[Task[_]]
}
def setOriginal(delegate: Task ~> Task): Task ~> Task =
new (Task ~> Task) {
def apply[T](in: Task[T]): Task[T] =
{
val transformed = delegate(in)
if( (transformed eq in) || transformed.info.original.isDefined)
transformed
else
transformed.copy(info = transformed.info.copy(original = in.info.original orElse Some(in)))
}
}
def apply[Input, State, Owner](dummies: Dummies[Input, State], injected: Injected[Input, State], context: Context[Owner]) =
{
@ -117,7 +128,7 @@ object Transform
import System._
import Convert._
val inputs = dummyMap(dummyIn, dummyState)(in, state)
Convert.taskToNode streamed(streams, dummyStreams) implied(owner, subs, static) name(staticName) getOrId(inputs)
Convert.taskToNode setOriginal(streamed(streams, dummyStreams)) implied(owner, subs, static) setOriginal(name(staticName)) getOrId(inputs)
}
}
object Convert
@ -126,13 +137,9 @@ object Convert
def apply[T](t: Task[T]): Node[Task, T] = t.work match {
case Pure(eval) => toNode(KNil)( _ => Right(eval()) )
case Mapped(in, f) => toNode(in)( right f )
case MapAll(in, f) => toNode(in)( right (f compose allM) )
case MapFailure(in, f) => toNode(in)( right (f compose anyFailM))
case FlatMapped(in, f) => toNode(in)( left f )
case FlatMapAll(in, f) => toNode(in)( left (f compose allM) )
case FlatMapFailure(in, f) => toNode(in)( left (f compose anyFailM) )
case DependsOn(in, deps) => toNode(KList.fromList(deps))( _ => Left(in) )
case Join(in, f) => uniform(in)(f compose all)
case Join(in, f) => uniform(in)(f)
}
}
@ -150,24 +157,4 @@ object Convert
val uniformIn = Nil
def work(results: Results[In], units: Seq[Result[Uniform]]) = f(results)
}
def anyFailM[In <: HList]: Results[In] => Seq[Incomplete] = in =>
{
val incs = failuresM(in)
if(incs.isEmpty) throw Incomplete(message = Some("Expected failure")) else incs
}
def allM[In <: HList]: Results[In] => In = in =>
{
val incs = failuresM(in)
if(incs.isEmpty) in.down(Result.tryValue) else throw Incomplete(causes = incs)
}
def all[D]: Seq[Result[D]] => Seq[D] = in =>
{
val incs = failures(in)
if(incs.isEmpty) in.map(Result.tryValue.fn[D]) else throw Incomplete(causes = incs)
}
def failuresM[In <: HList]: Results[In] => Seq[Incomplete] = x => failures[Any](x.toList)
def failures[A](results: Seq[Result[A]]): Seq[Incomplete] = results.collect { case Inc(i) => i }
}

View File

@ -13,7 +13,7 @@ sealed trait MultiInTask[In <: HList]
{
def flatMap[T](f: In => Task[T]): Task[T]
def flatMapR[T](f: Results[In] => Task[T]): Task[T]
def mapH[T](f: In => T): Task[T]
def map[T](f: In => T): Task[T]
def mapR[T](f: Results[In] => T): Task[T]
def flatFailure[T](f: Seq[Incomplete] => Task[T]): Task[T]
def mapFailure[T](f: Seq[Incomplete] => T): Task[T]
@ -51,14 +51,14 @@ import java.io._
sealed trait BinaryPipe
{
def binary[T](f: BufferedInputStream => T): Task[T]
//def binary[T](sid: String)(f: BufferedInputStream => T): Task[T]
def binary[T](sid: String)(f: BufferedInputStream => T): Task[T]
def #>(f: File): Task[Unit]
//def #>(sid: String, f: File): Task[Unit]
def #>(sid: String, f: File): Task[Unit]
}
sealed trait TextPipe
{
def text[T](f: BufferedReader => T): Task[T]
//def #| [T](sid: String)(f: BufferedReader => T): Task[T]
def text[T](sid: String)(f: BufferedReader => T): Task[T]
}
sealed trait TaskLines
{
@ -67,7 +67,7 @@ sealed trait TaskLines
}
sealed trait ProcessPipe {
def #| (p: ProcessBuilder): Task[Int]
//def #| (sid: String)(p: ProcessBuilder): Task[Int]
def pipe(sid: String)(p: ProcessBuilder): Task[Int]
}
trait TaskExtra
@ -84,35 +84,36 @@ trait TaskExtra
}
final implicit def pureJoin[S](in: Seq[S]): JoinTask[S, Seq] = joinTasks(pureTasks(in))
final implicit def joinTasks[S](in: Seq[Task[S]]): JoinTask[S, Seq] = new JoinTask[S, Seq] {
def join: Task[Seq[S]] = new Join(in, (s: Seq[S]) => Right(s) )
//def join[T](f: Iterable[S] => T): Task[Iterable[T]] = new MapAll( MList.fromTCList[Task](in), ml => f(ml.toList))
//def joinR[T](f: Iterable[Result[S]] => T): Task[Iterable[Result[T]]] = new Mapped( MList.fromTCList[Task](in), ml => f(ml.toList))
def join: Task[Seq[S]] = new Join(in, (s: Seq[Result[S]]) => Right(TaskExtra.all(s)) )
def reduce(f: (S,S) => S): Task[S] = TaskExtra.reduce(in.toIndexedSeq, f)
}
final implicit def multInputTask[In <: HList](tasks: Tasks[In]): MultiInTask[In] = new MultiInTask[In] {
def flatMap[T](f: In => Task[T]): Task[T] = new FlatMapAll(tasks, f)
import TaskExtra.{allM, anyFailM}
def flatMapR[T](f: Results[In] => Task[T]): Task[T] = new FlatMapped(tasks, f)
def mapH[T](f: In => T): Task[T] = new MapAll(tasks, f)
def flatMap[T](f: In => Task[T]): Task[T] = flatMapR(f compose allM)
def flatFailure[T](f: Seq[Incomplete] => Task[T]): Task[T] = flatMapR(f compose anyFailM)
def mapR[T](f: Results[In] => T): Task[T] = new Mapped(tasks, f)
def flatFailure[T](f: Seq[Incomplete] => Task[T]): Task[T] = new FlatMapFailure(tasks, f)
def mapFailure[T](f: Seq[Incomplete] => T): Task[T] = new MapFailure(tasks, f)
def map[T](f: In => T): Task[T] = mapR(f compose allM)
def mapFailure[T](f: Seq[Incomplete] => T): Task[T] = mapR(f compose anyFailM)
}
final implicit def singleInputTask[S](in: Task[S]): SingleInTask[S] = new SingleInTask[S] {
import TaskExtra.{successM, failM}
type HL = S :+: HNil
private val ml = in :^: KNil
private def headM = (_: Results[HL]).combine.head
private def headH = (_: HL).head
private def headS = (_: Seq[Incomplete]).head
def flatMapR[T](f: Result[S] => Task[T]): Task[T] = new FlatMapped[T, HL](ml, f headM)
def flatMap[T](f: S => Task[T]): Task[T] = new FlatMapAll(ml, f headH)
def flatFailure[T](f: Incomplete => Task[T]): Task[T] = new FlatMapFailure(ml, f headS)
def map[T](f: S => T): Task[T] = new MapAll(ml, f headH)
def flatMap[T](f: S => Task[T]): Task[T] = flatMapR(f compose successM)
def flatFailure[T](f: Incomplete => Task[T]): Task[T] = flatMapR(f compose failM)
def mapR[T](f: Result[S] => T): Task[T] = new Mapped[T, HL](ml, f headM)
def mapFailure[T](f: Incomplete => T): Task[T] = new MapFailure(ml, f headS)
def map[T](f: S => T): Task[T] = mapR(f compose successM)
def mapFailure[T](f: Incomplete => T): Task[T] = mapR(f compose failM)
def dependsOn(tasks: Task[_]*): Task[S] = new DependsOn(in, tasks)
def andFinally(fin: => Unit): Task[S] = mapR(x => Result.tryValue[S]( { fin; x }))
@ -121,14 +122,14 @@ trait TaskExtra
def && [T](alt: Task[T]): Task[T] = flatMap( _ => alt )
}
final implicit def toTaskInfo[S](in: Task[S]): TaskInfo[S] = new TaskInfo[S] {
def named(s: String): Task[S] = in.copy(info = in.info.copy(name = Some(s)))
def describedAs(s: String): Task[S] = in.copy(info = in.info.copy(description = Some(s)))
def implies: Task[S] = in.copy(info = in.info.copy(implied = true))
def named(s: String): Task[S] = in.copy(info = in.info.setName(s))
def describedAs(s: String): Task[S] = in.copy(info = in.info.setDescription(s))
def implies: Task[S] = in.copy(info = in.info.setImplied(true))
}
final implicit def pipeToProcess(t: Task[_])(implicit streams: Task[TaskStreams]): ProcessPipe = new ProcessPipe {
def #| (p: ProcessBuilder): Task[Int] = pipe0(None, p)
//def #| (sid: String)(p: ProcessBuilder): Task[Int] = pipe0(Some(sid), p)
def pipe(sid: String)(p: ProcessBuilder): Task[Int] = pipe0(Some(sid), p)
private def pipe0(sid: Option[String], p: ProcessBuilder): Task[Int] =
for(s <- streams; in <- s.readBinary(t, sid, true)) yield {
val pio = TaskExtra.processIO(s).withInput( out => { BasicIO.transferFully(in, out); out.close() } )
@ -138,10 +139,10 @@ trait TaskExtra
final implicit def binaryPipeTask(in: Task[_])(implicit streams: Task[TaskStreams]): BinaryPipe = new BinaryPipe {
def binary[T](f: BufferedInputStream => T): Task[T] = pipe0(None, f)
//def #| [T](sid: String)(f: BufferedInputStream => T): Task[T] = pipe0(Some(sid), f)
def binary[T](sid: String)(f: BufferedInputStream => T): Task[T] = pipe0(Some(sid), f)
def #>(f: File): Task[Unit] = pipe0(None, toFile(f))
//def #>(sid: String, f: File): Task[Unit] = pipe0(Some(sid), toFile(f))
def #>(sid: String, f: File): Task[Unit] = pipe0(Some(sid), toFile(f))
private def pipe0 [T](sid: Option[String], f: BufferedInputStream => T): Task[T] =
streams flatMap { s => s.readBinary(in, sid, true) map f }
@ -150,7 +151,7 @@ trait TaskExtra
}
final implicit def textPipeTask(in: Task[_])(implicit streams: Task[TaskStreams]): TextPipe = new TextPipe {
def text[T](f: BufferedReader => T): Task[T] = pipe0(None, f)
//def #| [T](sid: String)(f: BufferedReader => T): Task[T] = pipe0(Some(sid), f)
def text [T](sid: String)(f: BufferedReader => T): Task[T] = pipe0(Some(sid), f)
private def pipe0 [T](sid: Option[String], f: BufferedReader => T): Task[T] =
streams flatMap { s => s.readText(in, sid, true) map f }
@ -185,5 +186,29 @@ object TaskExtra extends TaskExtra
reducePair( reduce(a, f), reduce(b, f), f )
}
def reducePair[S](a: Task[S], b: Task[S], f: (S, S) => S): Task[S] =
(a :^: b :^: KNil) mapH { case x :+: y :+: HNil => f(x,y) }
(a :^: b :^: KNil) map { case x :+: y :+: HNil => f(x,y) }
def anyFailM[In <: HList]: Results[In] => Seq[Incomplete] = in =>
{
val incs = failuresM(in)
if(incs.isEmpty) expectedFailure else incs
}
def failM[T]: Result[T] => Incomplete = { case Inc(i) => i; case x => expectedFailure }
def expectedFailure = throw Incomplete(message = Some("Expected failure"))
def successM[T]: Result[T] => T = { case Inc(i) => throw i; case Value(t) => t }
def allM[In <: HList]: Results[In] => In = in =>
{
val incs = failuresM(in)
if(incs.isEmpty) in.down(Result.tryValue) else throw Incomplete(causes = incs)
}
def failuresM[In <: HList]: Results[In] => Seq[Incomplete] = x => failures[Any](x.toList)
def all[D](in: Seq[Result[D]]) =
{
val incs = failures(in)
if(incs.isEmpty) in.map(Result.tryValue.fn[D]) else throw Incomplete(causes = incs)
}
def failures[A](results: Seq[Result[A]]): Seq[Incomplete] = results.collect { case Inc(i) => i }
}

View File

@ -0,0 +1,34 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
import Types._
// T must be invariant to work properly.
// Because it is sealed and the only instances go through make,
// a single AttributeKey instance cannot conform to AttributeKey[T] for different Ts
sealed trait AttributeKey[T]
object AttributeKey
{
def make[T]: AttributeKey[T] = new AttributeKey[T] {}
}
trait AttributeMap
{
def apply[T](k: AttributeKey[T]): T
def get[T](k: AttributeKey[T]): Option[T]
def contains[T](k: AttributeKey[T]): Boolean
def put[T](k: AttributeKey[T], value: T): AttributeMap
}
object AttributeMap
{
def empty: AttributeMap = new BasicAttributeMap(Map.empty)
}
private class BasicAttributeMap(backing: Map[AttributeKey[_], Any]) extends AttributeMap
{
def apply[T](k: AttributeKey[T]) = backing(k).asInstanceOf[T]
def get[T](k: AttributeKey[T]) = backing.get(k).asInstanceOf[Option[T]]
def contains[T](k: AttributeKey[T]) = backing.contains(k)
def put[T](k: AttributeKey[T], value: T): AttributeMap = new BasicAttributeMap( backing.updated(k, value) )
}

View File

@ -9,21 +9,23 @@ import Types._
* type parameters HL. The underlying data is M applied to each type parameter.
* Explicitly tracking M[_] allows performing natural transformations or ensuring
* all data conforms to some common type. */
sealed trait KList[+M[_], +HL <: HList] {
sealed trait KList[+M[_], +HL <: HList]
{
type Raw = HL
/** Transform to the underlying HList type.*/
def down(implicit ev: M ~> Id): HL
/** Apply a natural transformation. */
def map[N[_]](f: M ~> N): KList[N, HL]
def transform[N[_]](f: M ~> N): KList[N, HL]
/** Convert to a List. */
def toList: List[M[_]]
/** Convert to an HList. */
def combine[N[X] >: M[X]]: HL#Wrap[N]
}
final case class KCons[H, T <: HList, +M[_]](head: M[H], tail: KList[M,T]) extends KList[M, H :+: T] {
def down(implicit f: M ~> Id) = HCons(f(head), tail.down(f))
def map[N[_]](f: M ~> N) = KCons( f(head), tail.map(f) )
final case class KCons[H, T <: HList, +M[_]](head: M[H], tail: KList[M,T]) extends KList[M, H :+: T]
{
def down(implicit f: M ~> Id) = HCons(f(head), tail down f)
def transform[N[_]](f: M ~> N) = KCons( f(head), tail transform f )
// prepend
def :^: [N[X] >: M[X], G](g: N[G]) = KCons(g, this)
def toList = head :: tail.toList
@ -31,9 +33,10 @@ final case class KCons[H, T <: HList, +M[_]](head: M[H], tail: KList[M,T]) exten
def combine[N[X] >: M[X]]: (H :+: T)#Wrap[N] = HCons(head, tail.combine)
}
sealed class KNil extends KList[Nothing, HNil] {
sealed class KNil extends KList[Nothing, HNil]
{
def down(implicit f: Nothing ~> Id) = HNil
def map[N[_]](f: Nothing ~> N) = KNil
def transform[N[_]](f: Nothing ~> N) = KNil
def :^: [M[_], H](h: M[H]) = KCons(h, this)
def toList = Nil
def combine[N[X]] = HNil

View File

@ -8,4 +8,8 @@ object Types extends TypeFunctions
val :^: = KCons
val :+: = HCons
type :+:[H, T <: HList] = HCons[H,T]
implicit def hconsToK[M[_], H, T <: HList](h: M[H] :+: T)(implicit mt: T => KList[M, T]): KList[M, H :+: T] =
KCons[H, T, M](h.head, mt(h.tail) )
implicit def hnilToK(hnil: HNil): KNil = KNil
}

View File

@ -9,7 +9,7 @@ object KTest {
val f = new (Option ~> List) { def apply[T](o: Option[T]): List[T] = o.toList }
val x = Some(3) :^: Some("asdf") :^: KNil
val y = x map f
val y = x transform f
val m1a = y match { case List(3) :^: List("asdf") :^: KNil => println("true") }
val m1b = (List(3) :^: KNil) match { case yy :^: KNil => println("true") }