Merge pull request #66 from eed3si9n/wip/212

Scala 2.12
This commit is contained in:
eugene yokota 2016-12-22 15:30:50 -05:00 committed by Dale Wijnand
commit 52bb767bc8
No known key found for this signature in database
GPG Key ID: 4F256E3D151DF5EF
3 changed files with 56 additions and 22 deletions

View File

@ -5,9 +5,37 @@ import scala.annotation.tailrec
// Copied from Cats (MIT license) // Copied from Cats (MIT license)
/** /**
* Eval is a datatype, which controls evaluation. * Eval is a monad which controls evaluation.
*
* This type wraps a value (or a computation that produces a value)
* and can produce it on command via the `.value` method.
*
* There are three basic evaluation strategies:
*
* - Now: evaluated immediately
* - Later: evaluated once when value is needed
* - Always: evaluated every time value is needed
*
* The Later and Always are both lazy strategies while Now is eager.
* Later and Always are distinguished from each other only by
* memoization: once evaluated Later will save the value to be returned
* immediately if it is needed again. Always will run its computation
* every time.
*
* Eval supports stack-safe lazy computation via the .map and .flatMap
* methods, which use an internal trampoline to avoid stack overflows.
* Computation done within .map and .flatMap is always done lazily,
* even when applied to a Now instance.
*
* It is not generally good style to pattern-match on Eval instances.
* Rather, use .map and .flatMap to chain computation, and use .value
* to get the result when needed. It is also not good style to create
* Eval instances whose computation involves calling .value on another
* Eval instance -- this can defeat the trampolining and lead to stack
* overflows.
*/ */
sealed abstract class Eval[A] extends Serializable { self => sealed abstract class Eval[+A] extends Serializable { self =>
/** /**
* Evaluate the computation and return an A value. * Evaluate the computation and return an A value.
* *
@ -15,7 +43,7 @@ sealed abstract class Eval[A] extends Serializable { self =>
* will be performed at this point. For eager instances (Now), a * will be performed at this point. For eager instances (Now), a
* value will be immediately returned. * value will be immediately returned.
*/ */
def get: A def value: A
/** /**
* Transform an Eval[A] into an Eval[B] given the transformation * Transform an Eval[A] into an Eval[B] given the transformation
@ -47,8 +75,11 @@ sealed abstract class Eval[A] extends Serializable { self =>
case c: Eval.Compute[A] => case c: Eval.Compute[A] =>
new Eval.Compute[B] { new Eval.Compute[B] {
type Start = c.Start type Start = c.Start
val start = c.start // See https://issues.scala-lang.org/browse/SI-9931 for an explanation
val run = (s: c.Start) => // of why the type annotations are necessary in these two lines on
// Scala 2.12.0.
val start: () => Eval[Start] = c.start
val run: Start => Eval[B] = (s: c.Start) =>
new Eval.Compute[B] { new Eval.Compute[B] {
type Start = A type Start = A
val start = () => c.run(s) val start = () => c.run(s)
@ -87,7 +118,7 @@ sealed abstract class Eval[A] extends Serializable { self =>
* This type should be used when an A value is already in hand, or * This type should be used when an A value is already in hand, or
* when the computation to produce an A value is pure and very fast. * when the computation to produce an A value is pure and very fast.
*/ */
final case class Now[A](get: A) extends Eval[A] { final case class Now[A](value: A) extends Eval[A] {
def memoize: Eval[A] = this def memoize: Eval[A] = this
} }
@ -115,7 +146,7 @@ final class Later[A](f: () => A) extends Eval[A] {
// //
// (For situations where `f` is small, but the output will be very // (For situations where `f` is small, but the output will be very
// expensive to store, consider using `Always`.) // expensive to store, consider using `Always`.)
lazy val get: A = { lazy val value: A = {
val result = thunk() val result = thunk()
thunk = null // scalastyle:off thunk = null // scalastyle:off
result result
@ -139,7 +170,7 @@ object Later {
* caching must be avoided. Generally, prefer Later. * caching must be avoided. Generally, prefer Later.
*/ */
final class Always[A](f: () => A) extends Eval[A] { final class Always[A](f: () => A) extends Eval[A] {
def get: A = f() def value: A = f()
def memoize: Eval[A] = new Later(f) def memoize: Eval[A] = new Later(f)
} }
@ -193,8 +224,8 @@ object Eval {
* they will be automatically created when needed. * they will be automatically created when needed.
*/ */
sealed abstract class Call[A](val thunk: () => Eval[A]) extends Eval[A] { sealed abstract class Call[A](val thunk: () => Eval[A]) extends Eval[A] {
def memoize: Eval[A] = new Later(() => get) def memoize: Eval[A] = new Later(() => value)
def get: A = Call.loop(this).get def value: A = Call.loop(this).value
} }
object Call { object Call {
@ -229,16 +260,16 @@ object Eval {
* *
* Unlike a traditional trampoline, the internal workings of the * Unlike a traditional trampoline, the internal workings of the
* trampoline are not exposed. This allows a slightly more efficient * trampoline are not exposed. This allows a slightly more efficient
* implementation of the .get method. * implementation of the .value method.
*/ */
sealed abstract class Compute[A] extends Eval[A] { sealed abstract class Compute[A] extends Eval[A] {
type Start type Start
val start: () => Eval[Start] val start: () => Eval[Start]
val run: Start => Eval[A] val run: Start => Eval[A]
def memoize: Eval[A] = Later(get) def memoize: Eval[A] = Later(value)
def get: A = { def value: A = {
type L = Eval[Any] type L = Eval[Any]
type C = Any => Eval[Any] type C = Any => Eval[Any]
@tailrec def loop(curr: L, fs: List[C]): Any = @tailrec def loop(curr: L, fs: List[C]): Any =
@ -251,12 +282,12 @@ object Eval {
cc.run.asInstanceOf[C] :: c.run.asInstanceOf[C] :: fs cc.run.asInstanceOf[C] :: c.run.asInstanceOf[C] :: fs
) )
case xx => case xx =>
loop(c.run(xx.get).asInstanceOf[L], fs) loop(c.run(xx.value), fs)
} }
case x => case x =>
fs match { fs match {
case f :: fs => loop(f(x.get), fs) case f :: fs => loop(f(x.value), fs)
case Nil => x.get case Nil => x.value
} }
} }
loop(this.asInstanceOf[L], Nil).asInstanceOf[A] loop(this.asInstanceOf[L], Nil).asInstanceOf[A]

View File

@ -10,7 +10,7 @@ final case class Scope(nestIndex: Int, idAtIndex: Int = 0)
// Lots of type constructors would become binary, which as you may know requires lots of type lambdas // Lots of type constructors would become binary, which as you may know requires lots of type lambdas
// when you want a type function with only one parameter. // when you want a type function with only one parameter.
// That would be a general pain.) // That would be a general pain.)
object SettingsExample extends Init[Scope] { case class SettingsExample() extends Init[Scope] {
// Provides a way of showing a Scope+AttributeKey[_] // Provides a way of showing a Scope+AttributeKey[_]
val showFullKey: Show[ScopedKey[_]] = new Show[ScopedKey[_]] { val showFullKey: Show[ScopedKey[_]] = new Show[ScopedKey[_]] {
def apply(key: ScopedKey[_]) = s"${key.scope.nestIndex}(${key.scope.idAtIndex})/${key.key.label}" def apply(key: ScopedKey[_]) = s"${key.scope.nestIndex}(${key.scope.idAtIndex})/${key.key.label}"
@ -30,8 +30,8 @@ object SettingsExample extends Init[Scope] {
/** Usage Example **/ /** Usage Example **/
object SettingsUsage { case class SettingsUsage(val settingsExample: SettingsExample) {
import SettingsExample._ import settingsExample._
// Define some keys // Define some keys
val a = AttributeKey[Int]("a") val a = AttributeKey[Int]("a")

View File

@ -2,10 +2,12 @@ package sbt.internal.util
import org.scalacheck._ import org.scalacheck._
import Prop._ import Prop._
import SettingsUsage._
import SettingsExample._
object SettingsTest extends Properties("settings") { object SettingsTest extends Properties("settings") {
val settingsExample: SettingsExample = SettingsExample()
import settingsExample._
val settingsUsage = SettingsUsage(settingsExample)
import settingsUsage._
import scala.reflect.Manifest import scala.reflect.Manifest
@ -126,7 +128,7 @@ object SettingsTest extends Properties("settings") {
// Each project defines an initial value, but the update is defined in globalKey. // Each project defines an initial value, but the update is defined in globalKey.
// However, the derived Settings that come from this should be scoped in each project. // However, the derived Settings that come from this should be scoped in each project.
val settings: Seq[Setting[_]] = val settings: Seq[Setting[_]] =
derive(setting(globalDerivedKey, SettingsExample.map(globalKey)(_ + 1))) +: projectKeys.map(pk => setting(pk, value(0))) derive(setting(globalDerivedKey, settingsExample.map(globalKey)(_ + 1))) +: projectKeys.map(pk => setting(pk, value(0)))
val ev = evaluate(settings) val ev = evaluate(settings)
// Also check that the key has no value at the "global" scope // Also check that the key has no value at the "global" scope
val props = for { pk <- projectDerivedKeys } yield checkKey(pk, Some(1), ev) val props = for { pk <- projectDerivedKeys } yield checkKey(pk, Some(1), ev)
@ -184,6 +186,7 @@ object SettingsTest extends Properties("settings") {
} }
// This setup is a workaround for module synchronization issues // This setup is a workaround for module synchronization issues
final class CCR(intermediate: Int) { final class CCR(intermediate: Int) {
import SettingsTest.settingsExample._
lazy val top = iterate(value(intermediate), intermediate) lazy val top = iterate(value(intermediate), intermediate)
def iterate(init: Initialize[Int], i: Int): Initialize[Int] = def iterate(init: Initialize[Int], i: Int): Initialize[Int] =
bind(init) { t => bind(init) { t =>