diff --git a/core/src/main/scala/scalaz/reactive/Time.scala b/core/src/main/scala/scalaz/reactive/Time.scala index e8a90d3..90db8dd 100644 --- a/core/src/main/scala/scalaz/reactive/Time.scala +++ b/core/src/main/scala/scalaz/reactive/Time.scala @@ -15,7 +15,7 @@ object Time extends TimeInstances0 { case object PosInf extends Time - def now = IO.sync(T(System.currentTimeMillis())) + def now: IO[Void, T] = IO.sync(T(System.currentTimeMillis())) } trait TimeInstances0 { diff --git a/core/src/test/scala/scalaz/reactive/ReactiveSpec.scala b/core/src/test/scala/scalaz/reactive/ReactiveSpec.scala new file mode 100644 index 0000000..13fefd7 --- /dev/null +++ b/core/src/test/scala/scalaz/reactive/ReactiveSpec.scala @@ -0,0 +1,57 @@ +package scalaz.reactive + +import org.scalacheck.Gen +import org.specs2.{ScalaCheck, Specification} +import scalaz._ +import scalaz.reactive.domain.TestDomain._ +import scalaz.reactive.Time.T +import scalaz.reactive.laws.ApplicativeLaws +import scalaz.zio.IO + +class ReactiveSpec + extends Specification + with ScalaCheck + with ReactiveInstances { + + // laws.apIdentityLaw fails, without ever reaching Reactive.ap function + + def is = "ReactiveInstances".title ^ s2""" + Generate a mix of K and Fun TimeFuns + `Reactive.ap` holds identity law. ${laws.apIdentityLaw} + """ + + def aGen: Gen[A] = + Gen.choose(-5L, 6L).map(i => if (i < 5) Right(i) else STOP) + + val stopEvent: Event[A] = + Event(IO.sync { (T(0), Reactive(STOP, stopEvent)) }) + + def genStopEvent: Gen[Event[A]] = + Gen.oneOf(List(stopEvent)) // how to choose from 1? + + def faGenEvent: Gen[Event[A]] = + for { + r <- faGenReactive + } yield Event(IO.sync(((T(0), r)))) + + def faGenReactive: Gen[Reactive[A]] = + for { + head <- aGen + tail <- if (head.isLeft) genStopEvent else faGenEvent + } yield Reactive(head, tail) + + def abGen: Gen[A => A] = ??? + + def fabGenBehaviour: Gen[Reactive[A => A]] = ??? + + val laws = + new ApplicativeLaws( + Applicative[Reactive], + aGen, + faGenReactive, + abGen, + fabGenBehaviour, + valueForComparisonReactive + ) + +} diff --git a/core/src/test/scala/scalaz/reactive/TimeFunSpec.scala b/core/src/test/scala/scalaz/reactive/TimeFunSpec.scala index 00bb621..06cbba0 100644 --- a/core/src/test/scala/scalaz/reactive/TimeFunSpec.scala +++ b/core/src/test/scala/scalaz/reactive/TimeFunSpec.scala @@ -1,7 +1,7 @@ package scalaz.reactive import org.scalacheck.Gen -import org.specs2.{ ScalaCheck, Specification } +import org.specs2.{ScalaCheck, Specification} import scalaz._ import scalaz.reactive.Time.T import scalaz.reactive.TimeFun._ @@ -23,8 +23,8 @@ class TimeFunSpec extends Specification with ScalaCheck with TimeFunInstances { def faGen = for { - v <- aGen - k <- aGen + v <- aGen + k <- aGen fun <- Gen.oneOf(K(v), Fun(_.value * k * v)) } yield fun @@ -38,11 +38,8 @@ class TimeFunSpec extends Specification with ScalaCheck with TimeFunInstances { k <- aGen } yield K((l: Long) => l + k) - def eqGen: Gen[TimeFun[Long] => Long] = - for { - l <- Gen.choose[Long](-100, 100) - t = T(l) - } yield (tf: TimeFun[Long]) => tf.apply(t) + def eqGen: TimeFun[Long] => Long = + tf => tf.apply(T(-1)) + tf.apply(T(0)) + tf.apply(T(1)) + tf.apply(T(10)) val laws = new ApplicativeLaws(Applicative[TimeFun], aGen, faGen, abGen, fabGen, eqGen) diff --git a/core/src/test/scala/scalaz/reactive/domain/TestDomain.scala b/core/src/test/scala/scalaz/reactive/domain/TestDomain.scala new file mode 100644 index 0000000..9a09180 --- /dev/null +++ b/core/src/test/scala/scalaz/reactive/domain/TestDomain.scala @@ -0,0 +1,36 @@ +package scalaz.reactive.domain +import scalaz.reactive.Reactive +import scalaz.zio.{IO, RTS} + +object TestDomain extends RTS { + + type A = Either[Stop, Long] // type to use in specification + case class Stop() + + val STOP = Left(Stop()) + + /* + Compare two Reactives for equality. reduce reactive to list of its elements + */ + def valueForComparisonReactive: Reactive[A] => List[Long] = + r => { + println(s"cmpReactive: reactive = $r") + val toReduce = reduce(List(), r) + println(s"cmpReactive: toReduce = $toReduce") + unsafeRun(toReduce) + } + + def reduce(ls: List[Long], r: Reactive[A]): IO[Void, List[Long]] = { + println(s"Reducing $r") + r match { + case Reactive(Left(Stop()), _) => { println(s"Encountered STOP - result is $ls"); IO.now(ls)} + case Reactive(Right(l), tail) => { + println("flatmapping") + tail.value.flatMap { + case (_, rr: Reactive[A]) => { println("inside flatmap"); reduce(ls :+ l, rr) } + } + } + } + } + +} diff --git a/core/src/test/scala/scalaz/reactive/domain/TestDomainSpec.scala b/core/src/test/scala/scalaz/reactive/domain/TestDomainSpec.scala new file mode 100644 index 0000000..579d9a3 --- /dev/null +++ b/core/src/test/scala/scalaz/reactive/domain/TestDomainSpec.scala @@ -0,0 +1,39 @@ +package scalaz.reactive.domain +import org.specs2.specification.core.SpecStructure +import org.specs2.{ScalaCheck, Specification} +import scalaz.reactive.Time.T +import scalaz.reactive.domain.TestDomain.A +import scalaz.reactive.{Event, Reactive} +import scalaz.zio.IO + +class TestDomainSpec extends Specification with ScalaCheck { + override def is: SpecStructure = "TestDomain".title ^ s2""" + Reactive values get reduced correctly. ${testReduceReactive} + + """ + + def testReduceReactive = { + def r: Reactive[A] = + Reactive[A]( + Right(1), + Event( + IO.sync( + ( + T(0), + Reactive( + Right(2), + Event( + IO.sync( + (T(0), Reactive(TestDomain.STOP, Event(IO.sync((T(0), r))))) + ) + ) + ) + ) + ) + ) + ) + + TestDomain.valueForComparisonReactive(r) must beEqualTo(List(1, 2)) + + } +} diff --git a/core/src/test/scala/scalaz/reactive/laws/ApplicativeLaws.scala b/core/src/test/scala/scalaz/reactive/laws/ApplicativeLaws.scala index 7f613c5..0c8636e 100644 --- a/core/src/test/scala/scalaz/reactive/laws/ApplicativeLaws.scala +++ b/core/src/test/scala/scalaz/reactive/laws/ApplicativeLaws.scala @@ -1,30 +1,36 @@ package scalaz.reactive.laws -import org.scalacheck.{ Gen, Prop } +import org.scalacheck.{Gen, Prop} import org.scalacheck.Prop.forAll -import org.specs2.matcher.{ MatchResult, MustMatchers } +import org.specs2.matcher.{MatchResult, MustMatchers} import scalaz.Applicative -class ApplicativeLaws[F[_], A]( - applicative: Applicative[F], - aGen: Gen[A], - faGen: Gen[F[A]], - abGen: Gen[A => A], - fabGen: Gen[F[A => A]], - valueForEqGen: Gen[F[A] => A] -)(implicit - pa: MatchResult[A] => Prop, - pfa: MatchResult[F[A]] => Prop) - extends MustMatchers { +/** + * To run property based testson the Applictive laws on F[_] + * + * @param applicative + * + */ +class ApplicativeLaws[F[_], A, B](applicative: => Applicative[F], + aGen: => Gen[A], + faGen: => Gen[F[A]], + abGen: => Gen[A => A], + fabGen: => Gen[F[A => A]], + fComp: => F[A] => B)( + implicit + pfa: MatchResult[F[A]] => Prop, + pb: MatchResult[B] => Prop +) extends MustMatchers { - implicit class Equalable(v: F[A]) { - def valueForEq(f: F[A] => A) = f(v) + implicit class ExtractValueForEq(v: F[A]) { + def valueForEq: B = fComp(v) } - // ap(fa)(point(_)) == fa - def apIdentityLaw = forAll(faGen, valueForEqGen) { (fa, v) => - applicative - .ap(fa)(applicative.point(identity[A](_))) - .valueForEq(v) must beEqualTo(fa.valueForEq(v)) + // Identity law: ap(fa)(point(_)) == fa + def apIdentityLaw = forAll(faGen) { fa => + println(s"Checking identity of $fa") + val right: B = fa.valueForEq + val left: B = applicative.ap(fa)(applicative.point(identity[A](_))).valueForEq + left must beEqualTo(right) } // ap(point(a))(point(ab)) == point(ab(a)) @@ -43,8 +49,8 @@ class ApplicativeLaws[F[_], A]( } //map(fa)(ab) == ap(fa)(point(ab)) - def apDerivedMapLaw = forAll(faGen, abGen, valueForEqGen) { (fa, ab, v) => - applicative.map(fa)(ab).valueForEq(v) must - beEqualTo(applicative.ap(fa)(applicative.point(ab)).valueForEq(v)) + def apDerivedMapLaw = forAll(faGen, abGen) { (fa, ab) => + applicative.map(fa)(ab).valueForEq must + beEqualTo(applicative.ap(fa)(applicative.point(ab)).valueForEq) } } diff --git a/examples/src/main/scala/scalaz/reactive/examples/awt/AwtKeyAndMouse.scala b/examples/src/main/scala/scalaz/reactive/examples/awt/AwtKeyAndMouse.scala index 4cf4fa5..a163c98 100644 --- a/examples/src/main/scala/scalaz/reactive/examples/awt/AwtKeyAndMouse.scala +++ b/examples/src/main/scala/scalaz/reactive/examples/awt/AwtKeyAndMouse.scala @@ -20,7 +20,7 @@ object KeyboardAndTimeApp extends App with RTS { class KeyboardAndTimeApp(name: String) extends AwtApp(name) { - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) + setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE) val displayArea = new JTextArea buildPane()