mirror of https://github.com/sbt/sbt.git
Selective functor
This implements Selective functor for `Either[A, B]` "task" (`Initialize[Task[Either[A, B]]]`).
The selective functor allows an encoding of if-expression:
```
def ifS[A](
x: Def.Initialize[Task[Boolean]]
)(t: Def.Initialize[Task[A]])(e: Def.Initialize[Task[A]]): Def.Initialize[Task[A]]
```
The benefit of this approach is that task dependencies are still visible to inspect command.
This commit is contained in:
parent
4592493617
commit
2feecf8a1f
|
|
@ -14,6 +14,10 @@ object Classes {
|
|||
def map[S, T](f: S => T, v: M[S]): M[T]
|
||||
}
|
||||
|
||||
trait Selective[M[_]] extends Applicative[M] {
|
||||
def select[A, B](fab: M[Either[A, B]])(fn: M[A => B]): M[B]
|
||||
}
|
||||
|
||||
trait Monad[M[_]] extends Applicative[M] {
|
||||
def flatten[T](m: M[M[T]]): M[T]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,6 +235,41 @@ object Def extends Init[Scope] with TaskMacroExtra with InitializeImplicits {
|
|||
def inputTaskDyn[T](t: Def.Initialize[Task[T]]): Def.Initialize[InputTask[T]] =
|
||||
macro inputTaskDynMacroImpl[T]
|
||||
|
||||
private[sbt] def selectITask[A, B](
|
||||
fab: Initialize[Task[Either[A, B]]],
|
||||
fin: Initialize[Task[A => B]]
|
||||
): Initialize[Task[B]] =
|
||||
fab.zipWith(fin)((ab, in) => TaskExtra.select(ab, in))
|
||||
|
||||
import Scoped.syntax._
|
||||
|
||||
// derived from select
|
||||
private[sbt] def branchS[A, B, C](
|
||||
x: Def.Initialize[Task[Either[A, B]]]
|
||||
)(l: Def.Initialize[Task[A => C]])(r: Def.Initialize[Task[B => C]]): Def.Initialize[Task[C]] = {
|
||||
val lhs = {
|
||||
val innerLhs: Def.Initialize[Task[Either[A, Either[B, C]]]] =
|
||||
x.map((fab: Either[A, B]) => fab.right.map(Left(_)))
|
||||
val innerRhs: Def.Initialize[Task[A => Either[B, C]]] =
|
||||
l.map((fn: A => C) => fn.andThen(Right(_)))
|
||||
selectITask(innerLhs, innerRhs)
|
||||
}
|
||||
selectITask(lhs, r)
|
||||
}
|
||||
|
||||
// derived from select
|
||||
def ifS[A](
|
||||
x: Def.Initialize[Task[Boolean]]
|
||||
)(t: Def.Initialize[Task[A]])(e: Def.Initialize[Task[A]]): Def.Initialize[Task[A]] = {
|
||||
val condition: Def.Initialize[Task[Either[Unit, Unit]]] =
|
||||
x.map((p: Boolean) => if (p) Left(()) else Right(()))
|
||||
val left: Def.Initialize[Task[Unit => A]] =
|
||||
t.map((a: A) => { _ => a })
|
||||
val right: Def.Initialize[Task[Unit => A]] =
|
||||
e.map((a: A) => { _ => a })
|
||||
branchS(condition)(left)(right)
|
||||
}
|
||||
|
||||
// The following conversions enable the types Initialize[T], Initialize[Task[T]], and Task[T] to
|
||||
// be used in task and setting macros as inputs with an ultimate result of type T
|
||||
|
||||
|
|
|
|||
|
|
@ -315,6 +315,20 @@ object Scoped {
|
|||
final def ??[T >: S](or: => T): Initialize[T] = Def.optional(scopedKey)(_ getOrElse or)
|
||||
}
|
||||
|
||||
// Duplicated with ProjectExtra.
|
||||
private[sbt] object syntax {
|
||||
implicit def richInitializeTask[T](init: Initialize[Task[T]]): Scoped.RichInitializeTask[T] =
|
||||
new Scoped.RichInitializeTask(init)
|
||||
|
||||
implicit def richInitializeInputTask[T](
|
||||
init: Initialize[InputTask[T]]
|
||||
): Scoped.RichInitializeInputTask[T] =
|
||||
new Scoped.RichInitializeInputTask(init)
|
||||
|
||||
implicit def richInitialize[T](i: Initialize[T]): Scoped.RichInitialize[T] =
|
||||
new Scoped.RichInitialize[T](i)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an [[sbt.Def.Initialize]] instance to provide `map` and `flatMap` semantics.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
val condition = taskKey[Boolean]("")
|
||||
val trueAction = taskKey[Unit]("")
|
||||
val falseAction = taskKey[Unit]("")
|
||||
val foo = taskKey[Unit]("")
|
||||
val output = settingKey[File]("")
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.settings(
|
||||
name := "ifs",
|
||||
output := baseDirectory.value / "output.txt",
|
||||
condition := true,
|
||||
trueAction := { IO.write(output.value, s"true\n", append = true) },
|
||||
falseAction := { IO.write(output.value, s"false\n", append = true) },
|
||||
foo := (Def.ifS(condition)(trueAction)(falseAction)).value,
|
||||
TaskKey[Unit]("check") := {
|
||||
val lines = IO.read(output.value).linesIterator.toList
|
||||
assert(lines == List("true"))
|
||||
()
|
||||
},
|
||||
TaskKey[Unit]("check2") := {
|
||||
val lines = IO.read(output.value).linesIterator.toList
|
||||
assert(lines == List("false"))
|
||||
()
|
||||
},
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
> foo
|
||||
> check
|
||||
|
||||
$ delete output.txt
|
||||
> set condition := false
|
||||
> foo
|
||||
> check2
|
||||
|
|
@ -57,6 +57,31 @@ final case class Join[T, U](in: Seq[Task[U]], f: Seq[Result[U]] => Either[Task[T
|
|||
Join[T, U](in.map(g.fn[U]), sr => f(sr).left.map(g.fn[T]))
|
||||
}
|
||||
|
||||
/**
|
||||
* A computation that conditionally falls back to a second transformation.
|
||||
* This can be used to encode `if` conditions.
|
||||
*/
|
||||
final case class Selected[A, B](fab: Task[Either[A, B]], fin: Task[A => B]) extends Action[B] {
|
||||
private def ml = AList.single[Either[A, B]]
|
||||
type K[L[x]] = L[Either[A, B]]
|
||||
|
||||
private[sbt] def mapTask(g: Task ~> Task) =
|
||||
Selected[A, B](g(fab), g(fin))
|
||||
|
||||
/**
|
||||
* Encode this computation as a flatMap.
|
||||
*/
|
||||
private[sbt] def asFlatMapped: FlatMapped[B, K] = {
|
||||
val f: Either[A, B] => Task[B] = {
|
||||
case Right(b) => std.TaskExtra.task(b)
|
||||
case Left(a) => std.TaskExtra.singleInputTask(fin).map(_(a))
|
||||
}
|
||||
FlatMapped[B, K](fab, {
|
||||
f compose std.TaskExtra.successM
|
||||
}, ml)
|
||||
}
|
||||
}
|
||||
|
||||
/** Combines metadata `info` and a computation `work` to define a task. */
|
||||
final case class Task[T](info: Info[T], work: Action[T]) {
|
||||
override def toString = info.name getOrElse ("Task(" + info + ")")
|
||||
|
|
|
|||
|
|
@ -146,10 +146,7 @@ trait TaskExtra {
|
|||
def failure: Task[Incomplete] = mapFailure(idFun)
|
||||
def result: Task[Result[S]] = mapR(idFun)
|
||||
|
||||
// The "taskDefinitionKey" is used, at least, by the ".previous" functionality.
|
||||
// But apparently it *cannot* survive a task map/flatMap/etc. See actions/depends-on.
|
||||
private def newInfo[A]: Info[A] =
|
||||
Info[A](AttributeMap(in.info.attributes.entries.filter(_.key.label != "taskDefinitionKey")))
|
||||
private def newInfo[A]: Info[A] = TaskExtra.newInfo(in.info)
|
||||
|
||||
def flatMapR[T](f: Result[S] => Task[T]): Task[T] =
|
||||
Task(newInfo, new FlatMapped[T, K](in, f, ml))
|
||||
|
|
@ -285,5 +282,13 @@ object TaskExtra extends TaskExtra {
|
|||
|
||||
def incompleteDeps(incs: Seq[Incomplete]): Incomplete = Incomplete(None, causes = incs)
|
||||
|
||||
def select[A, B](fab: Task[Either[A, B]], f: Task[A => B]): Task[B] =
|
||||
Task(newInfo(fab.info), new Selected[A, B](fab, f))
|
||||
|
||||
// The "taskDefinitionKey" is used, at least, by the ".previous" functionality.
|
||||
// But apparently it *cannot* survive a task map/flatMap/etc. See actions/depends-on.
|
||||
private[sbt] def newInfo[A](info: Info[_]): Info[A] =
|
||||
Info[A](AttributeMap(info.attributes.entries.filter(_.key.label != "taskDefinitionKey")))
|
||||
|
||||
private[sbt] def existToAny(in: Seq[Task[_]]): Seq[Task[Any]] = in.asInstanceOf[Seq[Task[Any]]]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ object Transform {
|
|||
case Pure(eval, _) => uniform(Nil)(_ => Right(eval()))
|
||||
case m: Mapped[t, k] => toNode[t, k](m.in)(right ∙ m.f)(m.alist)
|
||||
case m: FlatMapped[t, k] => toNode[t, k](m.in)(left ∙ m.f)(m.alist)
|
||||
case s: Selected[_, t] => val m = s.asFlatMapped; toNode(m.in)(left ∙ m.f)(m.alist)
|
||||
case DependsOn(in, deps) => uniform(existToAny(deps))(const(Left(in)) compose all)
|
||||
case Join(in, f) => uniform(in)(f)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue