From 344c5aea9a5dafe77c9b095a894542e60f43fd8d Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Sun, 4 Dec 2022 03:38:01 +0100 Subject: [PATCH 01/44] make reactive state variables lazy --- colibri/src/main/scala/colibri/Subject.scala | 8 + .../colibri/reactive/OwnedPlatform.scala | 9 - .../colibri/reactive/OwnerPlatform.scala | 20 +- .../scala-2/colibri/reactive/RxPlatform.scala | 2 +- .../reactive/internal/MacroUtils.scala | 53 +- .../colibri/reactive/OwnedPlatform.scala | 8 - .../colibri/reactive/OwnerPlatform.scala | 2 - .../scala-3/colibri/reactive/RxPlatform.scala | 2 +- .../main/scala/colibri/reactive/Owned.scala | 12 - .../main/scala/colibri/reactive/Owner.scala | 72 +- .../scala/colibri/reactive/Reactive.scala | 178 ++--- .../scala/colibri/reactive/implicits.scala | 16 - .../src/test/scala/colibri/ReactiveSpec.scala | 648 +++++++++++------- 13 files changed, 552 insertions(+), 478 deletions(-) delete mode 100644 reactive/src/main/scala-2/colibri/reactive/OwnedPlatform.scala delete mode 100644 reactive/src/main/scala-3/colibri/reactive/OwnedPlatform.scala delete mode 100644 reactive/src/main/scala/colibri/reactive/Owned.scala delete mode 100644 reactive/src/main/scala/colibri/reactive/implicits.scala diff --git a/colibri/src/main/scala/colibri/Subject.scala b/colibri/src/main/scala/colibri/Subject.scala index a8e3cba7..330ce243 100644 --- a/colibri/src/main/scala/colibri/Subject.scala +++ b/colibri/src/main/scala/colibri/Subject.scala @@ -13,6 +13,10 @@ final class ReplayLatestSubject[A] extends Observer[A] with Observable.MaybeValu @inline def now(): Option[A] = current + def unsafeResetState(): Unit = { + current = None + } + def unsafeOnNext(value: A): Unit = { current = Some(value) state.unsafeOnNext(value) @@ -37,6 +41,10 @@ final class ReplayAllSubject[A] extends Observer[A] with Observable[A] { @inline def now(): Seq[A] = current.toSeq + def unsafeResetState(): Unit = { + current.clear() + } + def unsafeOnNext(value: A): Unit = { current += value state.unsafeOnNext(value) diff --git a/reactive/src/main/scala-2/colibri/reactive/OwnedPlatform.scala b/reactive/src/main/scala-2/colibri/reactive/OwnedPlatform.scala deleted file mode 100644 index 891830a2..00000000 --- a/reactive/src/main/scala-2/colibri/reactive/OwnedPlatform.scala +++ /dev/null @@ -1,9 +0,0 @@ -package colibri.reactive - -import colibri.reactive.internal.MacroUtils -import colibri.effect.SyncEmbed -import colibri.SubscriptionOwner - -trait OwnedPlatform { - def apply[R: SubscriptionOwner: SyncEmbed](f: R): R = macro MacroUtils.ownedImpl[R] -} diff --git a/reactive/src/main/scala-2/colibri/reactive/OwnerPlatform.scala b/reactive/src/main/scala-2/colibri/reactive/OwnerPlatform.scala index ab9ad212..db505c9b 100644 --- a/reactive/src/main/scala-2/colibri/reactive/OwnerPlatform.scala +++ b/reactive/src/main/scala-2/colibri/reactive/OwnerPlatform.scala @@ -2,26 +2,14 @@ package colibri.reactive import colibri.{Observable, Cancelable} -trait OwnerPlatform { - @annotation.compileTimeOnly( - "No implicit Owner is available here! Wrap inside `Owned { }`, or provide an implicit `Owner`, or `import Owner.unsafeImplicits._` (dangerous).", - ) - implicit object compileTimeMock extends Owner { - def unsafeSubscribe(): Cancelable = ??? - def unsafeOwn(subscription: () => Cancelable): Unit = ??? - def cancelable: Cancelable = ??? - } -} - trait LiveOwnerPlatform { @annotation.compileTimeOnly( "No implicit LiveOwner is available here! Wrap inside `Rx { }`, or provide an implicit `LiveOwner`.", ) implicit object compileTimeMock extends LiveOwner { - def unsafeSubscribe(): Cancelable = ??? - def unsafeOwn(subscription: () => Cancelable): Unit = ??? - def cancelable: Cancelable = ??? - def unsafeLive[A](rx: Rx[A]): A = ??? - def liveObservable: Observable[Any] = ??? + def cancelable: Cancelable = ??? + def unsafeNow[A](rx: Rx[A]): A = ??? + def unsafeLive[A](rx: Rx[A]): A = ??? + def liveObservable: Observable[Any] = ??? } } diff --git a/reactive/src/main/scala-2/colibri/reactive/RxPlatform.scala b/reactive/src/main/scala-2/colibri/reactive/RxPlatform.scala index 8aa1c5e2..bea3f090 100644 --- a/reactive/src/main/scala-2/colibri/reactive/RxPlatform.scala +++ b/reactive/src/main/scala-2/colibri/reactive/RxPlatform.scala @@ -3,5 +3,5 @@ package colibri.reactive import colibri.reactive.internal.MacroUtils trait RxPlatform { - def apply[R](f: R)(implicit owner: Owner): Rx[R] = macro MacroUtils.rxImpl[R] + def apply[R](f: R): Rx[R] = macro MacroUtils.rxImpl[R] } diff --git a/reactive/src/main/scala-2/colibri/reactive/internal/MacroUtils.scala b/reactive/src/main/scala-2/colibri/reactive/internal/MacroUtils.scala index 480029ce..e5d22fda 100644 --- a/reactive/src/main/scala-2/colibri/reactive/internal/MacroUtils.scala +++ b/reactive/src/main/scala-2/colibri/reactive/internal/MacroUtils.scala @@ -1,30 +1,23 @@ package colibri.reactive.internal -import colibri.reactive.{Rx, Owner, LiveOwner} -import colibri.effect.SyncEmbed -import colibri.SubscriptionOwner +import colibri.reactive.{Rx, NowOwner, LiveOwner} import scala.reflect.macros._ // Inspired by scala.rx object MacroUtils { - private val ownerName = "colibriOwner" - private val liveOwnerName = "colibriLiveOwner" - - def injectOwner[T](c: blackbox.Context)(src: c.Tree, newOwner: c.universe.TermName, exceptOwner: c.Type): c.Tree = { + def injectOwner[T](c: blackbox.Context)(src: c.Tree, newOwner: c.universe.TermName, replaces: Set[c.Type]): c.Tree = { import c.universe._ - val implicitOwnerAtCaller = c.inferImplicitValue(typeOf[Owner], silent = false) - val implicitLiveOwnerAtCaller = c.inferImplicitValue(typeOf[LiveOwner], silent = false) + val implicitOwnerAtCallers = replaces.map(c.inferImplicitValue(_, silent = false)) object transformer extends c.universe.Transformer { override def transform(tree: c.Tree): c.Tree = { val shouldReplaceOwner = tree != null && tree.isTerm && - (tree.tpe =:= implicitOwnerAtCaller.tpe || tree.tpe =:= implicitLiveOwnerAtCaller.tpe) && - tree.tpe <:< typeOf[Owner] && - !(tree.tpe =:= typeOf[Nothing]) && - !(tree.tpe <:< exceptOwner) + implicitOwnerAtCallers.exists(_.tpe =:= tree.tpe) && + tree.tpe <:< typeOf[NowOwner] && + !(tree.tpe =:= typeOf[Nothing]) if (shouldReplaceOwner) q"$newOwner" else super.transform(tree) @@ -33,37 +26,23 @@ object MacroUtils { transformer.transform(src) } - def ownedImpl[R]( - c: blackbox.Context, - )(f: c.Expr[R])(subscriptionOwner: c.Expr[SubscriptionOwner[R]], syncEmbed: c.Expr[SyncEmbed[R]]): c.Expr[R] = { + def rxImpl[R](c: blackbox.Context)(f: c.Expr[R]): c.Expr[Rx[R]] = { import c.universe._ - val newOwner = c.freshName(TermName(ownerName)) + val newOwner = c.freshName(TermName("colibriLiveOwner")) - val newTree = c.untypecheck(injectOwner(c)(f.tree, newOwner, typeOf[LiveOwner])) - - val tree = q""" - _root_.colibri.reactive.Owned.function { ($newOwner: _root_.colibri.reactive.Owner) => - $newTree - }($subscriptionOwner, $syncEmbed) - """ - - // println(tree) - - c.Expr(tree) - } - - def rxImpl[R](c: blackbox.Context)(f: c.Expr[R])(owner: c.Expr[Owner]): c.Expr[Rx[R]] = { - import c.universe._ - - val newOwner = c.freshName(TermName(liveOwnerName)) - - val newTree = c.untypecheck(injectOwner(c)(f.tree, newOwner, typeOf[Nothing])) + val newTree = c.untypecheck( + injectOwner(c)( + f.tree, + newOwner, + replaces = Set(typeOf[LiveOwner], typeOf[NowOwner]), + ), + ) val tree = q""" _root_.colibri.reactive.Rx.function { ($newOwner: _root_.colibri.reactive.LiveOwner) => $newTree - }($owner) + } """ // println(tree) diff --git a/reactive/src/main/scala-3/colibri/reactive/OwnedPlatform.scala b/reactive/src/main/scala-3/colibri/reactive/OwnedPlatform.scala deleted file mode 100644 index 31a4914d..00000000 --- a/reactive/src/main/scala-3/colibri/reactive/OwnedPlatform.scala +++ /dev/null @@ -1,8 +0,0 @@ -package colibri.reactive - -import colibri.effect.SyncEmbed -import colibri.SubscriptionOwner - -trait OwnedPlatform { - def apply[R: SubscriptionOwner: SyncEmbed](f: Owner ?=> R): R = Owned.function(implicit owner => f) -} diff --git a/reactive/src/main/scala-3/colibri/reactive/OwnerPlatform.scala b/reactive/src/main/scala-3/colibri/reactive/OwnerPlatform.scala index 3c8f4ef2..0248f72f 100644 --- a/reactive/src/main/scala-3/colibri/reactive/OwnerPlatform.scala +++ b/reactive/src/main/scala-3/colibri/reactive/OwnerPlatform.scala @@ -1,5 +1,3 @@ package colibri.reactive -trait OwnerPlatform - trait LiveOwnerPlatform diff --git a/reactive/src/main/scala-3/colibri/reactive/RxPlatform.scala b/reactive/src/main/scala-3/colibri/reactive/RxPlatform.scala index 3fc09170..1aff9b35 100644 --- a/reactive/src/main/scala-3/colibri/reactive/RxPlatform.scala +++ b/reactive/src/main/scala-3/colibri/reactive/RxPlatform.scala @@ -1,5 +1,5 @@ package colibri.reactive trait RxPlatform { - def apply[R](f: LiveOwner ?=> R)(implicit owner: Owner): Rx[R] = Rx.function(implicit owner => f) + def apply[R](f: LiveOwner ?=> R): Rx[R] = Rx.function(implicit owner => f) } diff --git a/reactive/src/main/scala/colibri/reactive/Owned.scala b/reactive/src/main/scala/colibri/reactive/Owned.scala deleted file mode 100644 index 2ee2f8b6..00000000 --- a/reactive/src/main/scala/colibri/reactive/Owned.scala +++ /dev/null @@ -1,12 +0,0 @@ -package colibri.reactive - -import colibri.effect.SyncEmbed -import colibri.SubscriptionOwner - -object Owned extends OwnedPlatform { - def function[R: SubscriptionOwner: SyncEmbed](f: Owner => R): R = SyncEmbed[R].delay { - val owner = Owner.unsafeHotRef() - val result = f(owner) - SubscriptionOwner[R].own(result)(owner.unsafeSubscribe) - } -} diff --git a/reactive/src/main/scala/colibri/reactive/Owner.scala b/reactive/src/main/scala/colibri/reactive/Owner.scala index b7bea9a7..ed9b1d32 100644 --- a/reactive/src/main/scala/colibri/reactive/Owner.scala +++ b/reactive/src/main/scala/colibri/reactive/Owner.scala @@ -2,68 +2,46 @@ package colibri.reactive import colibri._ -@annotation.implicitNotFound( - "No implicit Owner is available here! Wrap inside `Owned { }`, or provide an implicit `Owner`, or `import Owner.unsafeImplicits._` (dangerous).", -) -trait Owner { - def cancelable: Cancelable - def unsafeSubscribe(): Cancelable - def unsafeOwn(subscription: () => Cancelable): Unit +trait NowOwner { + def unsafeNow[A](rx: Rx[A]): A } -object Owner extends OwnerPlatform { - def unsafeHotRef(): Owner = new Owner { - val refCountBuilder = Cancelable.refCountBuilder() - var initialRef = refCountBuilder.ref() - - def cancelable: Cancelable = refCountBuilder - - def unsafeSubscribe(): Cancelable = if (initialRef == null) { - refCountBuilder.ref() - } else { - val result = initialRef - initialRef = null - result +object NowOwner { + implicit object global extends NowOwner { + def unsafeNow[A](rx: Rx[A]): A = { + val cancelable = rx.unsafeSubscribe() + try(rx.nowIfSubscribed()) finally(cancelable.unsafeCancel()) } - - def unsafeOwn(subscription: () => Cancelable): Unit = refCountBuilder.unsafeAdd(subscription) - } - - object unsafeGlobal extends Owner { - def cancelable: Cancelable = Cancelable.empty - def unsafeSubscribe(): Cancelable = Cancelable.empty - def unsafeOwn(subscription: () => Cancelable): Unit = { - subscription() - () - } - } - - object unsafeImplicits { - implicit def unsafeGlobalOwner: Owner = unsafeGlobal } } @annotation.implicitNotFound( "No implicit LiveOwner is available here! Wrap inside `Rx { }`, or provide an implicit `LiveOwner`.", ) -trait LiveOwner extends Owner { +trait LiveOwner extends NowOwner { + def cancelable: Cancelable + def liveObservable: Observable[Any] + def unsafeLive[A](rx: Rx[A]): A } object LiveOwner extends LiveOwnerPlatform { - def unsafeHotRef()(implicit parentOwner: Owner): LiveOwner = new LiveOwner { - val owner: Owner = Owner.unsafeHotRef() - parentOwner.unsafeOwn(() => owner.unsafeSubscribe()) + def unsafeHotRef(): LiveOwner = new LiveOwner { + private val ref = Cancelable.builder() - val liveObservableArray = new scala.scalajs.js.Array[Observable[Any]]() - val liveObservable: Observable[Any] = Observable.mergeIterable(liveObservableArray) + private val subject = Subject.publish[Any]() - def unsafeLive[A](rx: Rx[A]): A = { - liveObservableArray.push(rx.observable) - rx.now() + val cancelable: Cancelable = ref + + val liveObservable: Observable[Any] = subject + + def unsafeNow[A](rx: Rx[A]): A = { + ref.unsafeAdd(() => rx.observable.unsafeSubscribe()) + rx.nowIfSubscribed() } - def unsafeSubscribe(): Cancelable = owner.unsafeSubscribe() - def unsafeOwn(subscription: () => Cancelable): Unit = owner.unsafeOwn(subscription) - def cancelable: Cancelable = owner.cancelable + def unsafeLive[A](rx: Rx[A]): A = { + ref.unsafeAdd(() => rx.observable.via(subject).unsafeSubscribe()) + rx.nowIfSubscribed() + } } } diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 64c756ba..caf9cf0c 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -1,56 +1,87 @@ package colibri.reactive import cats.Monoid +import cats.effect.SyncIO import colibri._ import colibri.effect._ import monocle.{Iso, Lens, Prism} import scala.concurrent.Future import scala.reflect.ClassTag +import scala.util.control.NonFatal + +object RxMissingNowException + extends Exception("Missing current value inside an Rx. Make sure, the Rx has active subscriptions when calling nowIfSubscribed.") trait Rx[+A] { def observable: Observable[A] - def now(): A + def nowOption(): Option[A] + + def apply()(implicit owner: LiveOwner): A + def now()(implicit owner: NowOwner): A + + final def nowIfSubscribed(): A = nowOption().getOrElse(throw RxMissingNowException) - final def apply()(implicit liveOwner: LiveOwner): A = liveOwner.unsafeLive(this) + final def collect[B](f: PartialFunction[A, B])(seed: => B): Rx[B] = transformRx(_.collect(f))(seed) + final def map[B](f: A => B): Rx[B] = transformRxSync(_.map(f)) + final def mapEither[B](f: A => Either[Throwable, B]): Rx[B] = transformRxSync(_.mapEither(f)) + final def tap(f: A => Unit): Rx[A] = transformRxSync(_.tap(f)) + final def tapLater(f: A => Unit): Rx[A] = transformRxSync(_.tail.tap(f)) - final def map[B](f: A => B)(implicit owner: Owner): Rx[B] = transformRxSync(_.map(f)) - final def mapEither[B](f: A => Either[Throwable, B])(implicit owner: Owner): Rx[B] = transformRxSync(_.mapEither(f)) - final def tap(f: A => Unit)(implicit owner: Owner): Rx[A] = transformRxSync(_.tap(f)) + final def mapSyncEffect[F[_]: RunSyncEffect, B](f: A => F[B]): Rx[B] = transformRxSync(_.mapEffect(f)) + final def mapEffect[F[_]: RunEffect, B](f: A => F[B])(seed: => B): Rx[B] = transformRx(_.mapEffect(f))(seed) + final def mapFuture[B](f: A => Future[B])(seed: => B): Rx[B] = transformRx(_.mapFuture(f))(seed) - final def collect[B](f: PartialFunction[A, B])(seed: => B)(implicit owner: Owner): Rx[B] = transformRx(_.collect(f))(seed) + final def as[B](value: B): Rx[B] = transformRxSync(_.as(value)) + final def asEval[B](value: => B): Rx[B] = transformRxSync(_.asEval(value)) - final def mapSyncEffect[F[_]: RunSyncEffect, B](f: A => F[B])(implicit owner: Owner): Rx[B] = transformRxSync(_.mapEffect(f)) - final def mapEffect[F[_]: RunEffect, B](f: A => F[B])(seed: => B)(implicit owner: Owner): Rx[B] = transformRx(_.mapEffect(f))(seed) - final def mapFuture[B](f: A => Future[B])(seed: => B)(implicit owner: Owner): Rx[B] = transformRx(_.mapFuture(f))(seed) + final def asSyncEffect[F[_]: RunSyncEffect, B](value: F[B]): Rx[B] = transformRxSync(_.asEffect(value)) + final def asEffect[F[_]: RunEffect, B](value: F[B])(seed: => B): Rx[B] = transformRx(_.asEffect(value))(seed) + final def asFuture[B](value: => Future[B])(seed: => B): Rx[B] = transformRx(_.asFuture(value))(seed) - final def as[B](value: B)(implicit owner: Owner): Rx[B] = transformRxSync(_.as(value)) - final def asEval[B](value: => B)(implicit owner: Owner): Rx[B] = transformRxSync(_.asEval(value)) + final def via(writer: RxWriter[A]): Rx[A] = transformRxSync(_.via(writer.observer)) - final def asSyncEffect[F[_]: RunSyncEffect, B](value: F[B])(implicit owner: Owner): Rx[B] = transformRxSync(_.asEffect(value)) - final def asEffect[F[_]: RunEffect, B](value: F[B])(seed: => B)(implicit owner: Owner): Rx[B] = transformRx(_.asEffect(value))(seed) - final def asFuture[B](value: => Future[B])(seed: => B)(implicit owner: Owner): Rx[B] = transformRx(_.asFuture(value))(seed) + final def switchMap[B](f: A => Rx[B]): Rx[B] = transformRxSync(_.switchMap(f andThen (_.observable))) + final def mergeMap[B](f: A => Rx[B]): Rx[B] = transformRxSync(_.mergeMap(f andThen (_.observable))) - final def switchMap[B](f: A => Rx[B])(implicit owner: Owner): Rx[B] = transformRxSync(_.switchMap(f andThen (_.observable))) - final def mergeMap[B](f: A => Rx[B])(implicit owner: Owner): Rx[B] = transformRxSync(_.mergeMap(f andThen (_.observable))) + final def transformRx[B](f: Observable[A] => Observable[B])(seed: => B): Rx[B] = Rx.observable(f(observable))(seed) + final def transformRxSync[B](f: Observable[A] => Observable[B]): Rx[B] = Rx.observableSync(f(observable)) - final def foreach(f: A => Unit)(implicit owner: Owner): Unit = owner.unsafeOwn(() => observable.unsafeForeach(f)) - final def foreachLater(f: A => Unit)(implicit owner: Owner): Unit = owner.unsafeOwn(() => observable.tail.unsafeForeach(f)) + final def hot: SyncIO[Rx[A]] = SyncIO(unsafeHot()) + + final def unsafeHot(): Rx[A] = { + val _ = unsafeSubscribe() + this + } - final def transformRx[B](f: Observable[A] => Observable[B])(seed: => B)(implicit owner: Owner): Rx[B] = Rx.observable(f(observable))(seed) - final def transformRxSync[B](f: Observable[A] => Observable[B])(implicit owner: Owner): Rx[B] = Rx.observableSync(f(observable)) + final def subscribe: SyncIO[Cancelable] = observable.subscribeSyncIO + + final def unsafeSubscribe(): Cancelable = observable.unsafeSubscribe() + final def unsafeSubscribe(writer: RxWriter[A]): Cancelable = observable.unsafeSubscribe(writer.observer) + + final def unsafeForeach(f: A => Unit): Cancelable = observable.unsafeForeach(f) + final def unsafeForeachLater(f: A => Unit): Cancelable = observable.tail.unsafeForeach(f) } object Rx extends RxPlatform { - def function[R](f: LiveOwner => R)(implicit owner: Owner): Rx[R] = { + def function[R](f: LiveOwner => R): Rx[R] = { val subject = Subject.behavior[Any](()) val observable = subject.switchMap { _ => - val liveOwner = LiveOwner.unsafeHotRef() - val result = f(liveOwner) - Observable[R](result) - .subscribing(liveOwner.liveObservable.dropSyncAll.head.via(subject)) - .tapCancel(liveOwner.cancelable.unsafeCancel) + val owner = LiveOwner.unsafeHotRef() + try { + val result = f(owner) + Observable[R](result) + .subscribing(owner.liveObservable.dropSyncAll.head.via(subject)) + .tapCancel(owner.cancelable.unsafeCancel) + } catch { + case NonFatal(t) => + owner.cancelable.unsafeCancel() + Observable.raiseError(t) + case t: Throwable => + owner.cancelable.unsafeCancel() + throw t + } } Rx.observableSync(observable) @@ -58,33 +89,32 @@ object Rx extends RxPlatform { def const[A](value: A): Rx[A] = new RxConst(value) - def observable[A](observable: Observable[A])(seed: => A)(implicit owner: Owner): Rx[A] = observableSync(observable.prependEval(seed)) + def observable[A](observable: Observable[A])(seed: => A): Rx[A] = observableSync(observable.prependEval(seed)) - def observableSync[A](observable: Observable[A])(implicit owner: Owner): Rx[A] = new RxObservableSync(observable) + def observableSync[A](observable: Observable[A]): Rx[A] = new RxObservableSync(observable) @inline implicit final class RxOps[A](private val self: Rx[A]) extends AnyVal { - def scan(f: (A, A) => A)(implicit owner: Owner): Rx[A] = scan(self.now())(f) + def scan(f: (A, A) => A): Rx[A] = scan(self.nowIfSubscribed())(f) - def scan[B](seed: B)(f: (B, A) => B)(implicit owner: Owner): Rx[B] = self.transformRxSync(_.scan0(seed)(f)) + def scan[B](seed: B)(f: (B, A) => B): Rx[B] = self.transformRxSync(_.scan0(seed)(f)) - def filter(f: A => Boolean)(seed: => A)(implicit owner: Owner): Rx[A] = self.transformRx(_.filter(f))(seed) + def filter(f: A => Boolean)(seed: => A): Rx[A] = self.transformRx(_.filter(f))(seed) } @inline implicit class RxBooleanOps(private val source: Rx[Boolean]) extends AnyVal { - @inline def toggle[A](ifTrue: => A, ifFalse: A)(implicit owner: Owner): Rx[A] = source.map { + @inline def toggle[A](ifTrue: => A, ifFalse: => A): Rx[A] = source.map { case true => ifTrue case false => ifFalse } - @inline def toggle[A: Monoid](ifTrue: => A)(implicit owner: Owner): Rx[A] = toggle(ifTrue, Monoid[A].empty) + @inline def toggle[A: Monoid](ifTrue: => A): Rx[A] = toggle(ifTrue, Monoid[A].empty) - @inline def negated(implicit owner: Owner): Rx[Boolean] = source.map(x => !x) + @inline def negated: Rx[Boolean] = source.map(x => !x) } implicit object source extends Source[Rx] { def unsafeSubscribe[A](source: Rx[A])(sink: Observer[A]): Cancelable = source.observable.unsafeSubscribe(sink) } - } trait RxWriter[-A] { @@ -119,38 +149,30 @@ object RxWriter { } } -trait Var[A] extends Rx[A] with RxWriter[A] { - final def update(f: A => A) = this.set(f(this.now())) +trait Var[A] extends RxWriter[A] with Rx[A] { + final def updateIfSubscribed(f: A => A): Unit = set(f(nowIfSubscribed())) + final def update(f: A => A): Unit = set(f(now())) final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: Rx[A] => Rx[A2]): Var[A2] = Var.combine(g(this), f(this)) final def transformVarRx(g: Rx[A] => Rx[A]): Var[A] = Var.combine(g(this), this) final def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.combine(this, f(this)) - final def imap[A2](f: A2 => A)(g: A => A2)(implicit owner: Owner): Var[A2] = transformVar(_.contramap(f))(_.map(g)) - final def lens[B](read: A => B)(write: (A, B) => A)(implicit owner: Owner): Var[B] = - transformVar(_.contramap(write(now(), _)))(_.map(read)) + final def imap[A2](f: A2 => A)(g: A => A2): Var[A2] = transformVar(_.contramap(f))(_.map(g)) + final def lens[B](read: A => B)(write: (A, B) => A): Var[B] = + transformVar(_.contramap(write(nowIfSubscribed(), _)))(_.map(read)) - final def prismInit[A2](f: A2 => A)(g: A => Option[A2])(initial: A2)(implicit owner: Owner): Var[A2] = - transformVar(_.contramap(f))(rx => Rx.observableSync(rx.observable.mapFilter(g).prepend(initial))) + final def prism[A2](f: A2 => A)(g: A => Option[A2])(seed: => A2): Var[A2] = + transformVar(_.contramap(f))(rx => Rx.observableSync(rx.observable.mapFilter(g).prependEval(seed))) - final def prism[A2](f: A2 => A)(g: A => Option[A2])(implicit owner: Owner): Option[Var[A2]] = - g(now()).map(prismInit(f)(g)(_)) - - final def subType[A2 <: A: ClassTag](implicit owner: Owner): Option[Var[A2]] = prism[A2]((x: A2) => x) { - case a: A2 => Some(a) - case _ => None - } - - final def subTypeInit[A2 <: A: ClassTag](initial: A2)(implicit owner: Owner): Var[A2] = prismInit[A2]((x: A2) => x) { + final def subType[A2 <: A: ClassTag](seed: => A2): Var[A2] = prism[A2]((x: A2) => x) { case a: A2 => Some(a) case _ => None - }(initial) + }(seed) - final def imapO[B](optic: Iso[A, B])(implicit owner: Owner): Var[B] = imap(optic.reverseGet(_))(optic.get(_)) - final def lensO[B](optic: Lens[A, B])(implicit owner: Owner): Var[B] = lens(optic.get(_))((base, zoomed) => optic.replace(zoomed)(base)) - final def prismO[B](optic: Prism[A, B])(implicit owner: Owner): Option[Var[B]] = prism(optic.reverseGet(_))(optic.getOption(_)) - final def prismInitO[B](optic: Prism[A, B])(initial: B)(implicit owner: Owner): Var[B] = - prismInit(optic.reverseGet(_))(optic.getOption(_))(initial) + final def imapO[B](optic: Iso[A, B]): Var[B] = imap(optic.reverseGet(_))(optic.get(_)) + final def lensO[B](optic: Lens[A, B]): Var[B] = lens(optic.get(_))((base, zoomed) => optic.replace(zoomed)(base)) + final def prismO[B](optic: Prism[A, B])(seed: => B): Var[B] = + prism(optic.reverseGet(_))(optic.getOption(_))(seed) } object Var { @@ -159,14 +181,14 @@ object Var { def combine[A](read: Rx[A], write: RxWriter[A]): Var[A] = new VarCombine(read, write) @inline implicit class SeqVarOperations[A](rxvar: Var[Seq[A]]) { - def sequence(implicit owner: Owner): Rx[Seq[Var[A]]] = Rx.observableSync(new Observable[Seq[Var[A]]] { + def sequence: Rx[Seq[Var[A]]] = Rx.observableSync(new Observable[Seq[Var[A]]] { def unsafeSubscribe(sink: Observer[Seq[Var[A]]]): Cancelable = { rxvar.observable.unsafeSubscribe( Observer.create( { seq => sink.unsafeOnNext(seq.zipWithIndex.map { case (a, idx) => - val observer = new Observer[A] { + val observer = new Observer[A] { def unsafeOnNext(value: A): Unit = { rxvar.set(seq.updated(idx, value)) } @@ -175,16 +197,7 @@ object Var { sink.unsafeOnError(error) } } - val observable = new Observable.Value[A] { - def now(): A = a - - def unsafeSubscribe(sink: Observer[A]): Cancelable = { - sink.unsafeOnNext(a) - Cancelable.empty - } - - } - Var.combine(Rx.observable(observable)(seed = a), RxWriter.observer(observer)) + Var.combine(Rx.const(a), RxWriter.observer(observer)) }) }, sink.unsafeOnError, @@ -195,7 +208,7 @@ object Var { } @inline implicit class OptionVarOperations[A](rxvar: Var[Option[A]]) { - def sequence(implicit owner: Owner): Rx[Option[Var[A]]] = Rx.observableSync(new Observable[Option[Var[A]]] { + def sequence: Rx[Option[Var[A]]] = Rx.observableSync(new Observable[Option[Var[A]]] { def unsafeSubscribe(outerSink: Observer[Option[Var[A]]]): Cancelable = { var cache = Option.empty[Var[A]] @@ -233,16 +246,19 @@ object Var { private final class RxConst[A](value: A) extends Rx[A] { val observable: Observable[A] = Observable.pure(value) - def now(): A = value + def nowOption(): Option[A] = Some(value) + def now()(implicit owner: NowOwner): A = value + def apply()(implicit owner: LiveOwner): A = value } -private final class RxObservableSync[A](inner: Observable[A])(implicit owner: Owner) extends Rx[A] { +private final class RxObservableSync[A](inner: Observable[A]) extends Rx[A] { private val state = new ReplayLatestSubject[A]() - val observable: Observable[A] = inner.dropUntilSyncLatest.distinctOnEquals.multicast(state).refCount - owner.unsafeOwn(() => observable.unsafeSubscribe()) + val observable: Observable[A] = inner.dropUntilSyncLatest.distinctOnEquals.tapCancel(state.unsafeResetState).multicast(state).refCount.distinctOnEquals - def now(): A = state.now().get + def nowOption() = state.now() + def now()(implicit owner: NowOwner) = owner.unsafeNow(this) + def apply()(implicit owner: LiveOwner): A = owner.unsafeLive(this) } private final class RxWriterObserver[A](val observer: Observer[A]) extends RxWriter[A] @@ -253,10 +269,14 @@ private final class VarSubject[A](seed: A) ext val observable: Observable[A] = state.distinctOnEquals val observer: Observer[A] = state - def now(): A = state.now() + def nowOption() = Some(state.now()) + def now()(implicit owner: NowOwner) = state.now() + def apply()(implicit owner: LiveOwner): A = owner.unsafeLive(this) } private final class VarCombine[A](innerRead: Rx[A], innerWrite: RxWriter[A]) extends Var[A] { - def now() = innerRead.now() - val observable = innerRead.observable - val observer = innerWrite.observer + def nowOption() = innerRead.nowOption() + def now()(implicit owner: NowOwner) = innerRead.now() + def apply()(implicit owner: LiveOwner): A = innerRead() + val observable = innerRead.observable + val observer = innerWrite.observer } diff --git a/reactive/src/main/scala/colibri/reactive/implicits.scala b/reactive/src/main/scala/colibri/reactive/implicits.scala deleted file mode 100644 index 8583c406..00000000 --- a/reactive/src/main/scala/colibri/reactive/implicits.scala +++ /dev/null @@ -1,16 +0,0 @@ -package colibri.reactive - -import colibri.{Observer, Observable} - -object implicits { - @inline class ObservableRxOps[A](private val self: Observable[A]) extends AnyVal { - def foreachOwned(f: A => Unit)(implicit owner: Owner): Unit = - subscribeOwned(Observer.foreach(f)) - - def subscribeOwned(rxWriter: RxWriter[A])(implicit owner: Owner): Unit = - subscribeOwned(rxWriter.observer) - - def subscribeOwned(observer: Observer[A])(implicit owner: Owner): Unit = - owner.unsafeOwn(() => self.unsafeSubscribe(observer)) - } -} diff --git a/reactive/src/test/scala/colibri/ReactiveSpec.scala b/reactive/src/test/scala/colibri/ReactiveSpec.scala index 7002d2a4..54be3f47 100644 --- a/reactive/src/test/scala/colibri/ReactiveSpec.scala +++ b/reactive/src/test/scala/colibri/ReactiveSpec.scala @@ -1,34 +1,25 @@ package colibri.reactive -import colibri._ import cats.implicits._ -import cats.effect.SyncIO import monocle.macros.{GenLens, GenPrism} import org.scalatest.matchers.should.Matchers import org.scalatest.flatspec.AsyncFlatSpec class ReactiveSpec extends AsyncFlatSpec with Matchers { - implicit def unsafeSubscriptionOwner[T]: SubscriptionOwner[SyncIO[T]] = new SubscriptionOwner[SyncIO[T]] { - def own(owner: SyncIO[T])(subscription: () => Cancelable): SyncIO[T] = - owner.flatTap(_ => SyncIO(subscription()).void) - } - - "Rx" should "map with proper subscription lifetime" in Owned(SyncIO { + "Rx" should "map with proper subscription lifetime" in { var mapped = List.empty[Int] var received1 = List.empty[Int] var received2 = List.empty[Int] - val owner = implicitly[Owner] - val variable = Var(1) val stream = variable.map { x => mapped ::= x; x } - mapped shouldBe List(1) + mapped shouldBe List.empty received1 shouldBe List.empty received2 shouldBe List.empty - stream.foreach(received1 ::= _) + val cancelR1 = stream.unsafeForeach(received1 ::= _) mapped shouldBe List(1) received1 shouldBe List(1) @@ -40,7 +31,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { received1 shouldBe List(2, 1) received2 shouldBe List.empty - stream.foreach(received2 ::= _) + val cancelR2 = stream.unsafeForeach(received2 ::= _) mapped shouldBe List(2, 1) received1 shouldBe List(2, 1) @@ -52,19 +43,14 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { received1 shouldBe List(3, 2, 1) received2 shouldBe List(3, 2) - val cancel = owner.unsafeSubscribe() - - mapped shouldBe List(3, 2, 1) - received1 shouldBe List(3, 2, 1) - received2 shouldBe List(3, 2) - variable.set(4) mapped shouldBe List(4, 3, 2, 1) received1 shouldBe List(4, 3, 2, 1) received2 shouldBe List(4, 3, 2) - cancel.unsafeCancel() + cancelR1.unsafeCancel() + cancelR2.unsafeCancel() mapped shouldBe List(4, 3, 2, 1) received1 shouldBe List(4, 3, 2, 1) @@ -76,7 +62,8 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { received1 shouldBe List(4, 3, 2, 1) received2 shouldBe List(4, 3, 2) - val cancel2 = owner.unsafeSubscribe() + val cancelR1b = stream.unsafeForeach(received1 ::= _) + val cancelR2b = stream.unsafeForeach(received2 ::= _) mapped shouldBe List(5, 4, 3, 2, 1) received1 shouldBe List(5, 4, 3, 2, 1) @@ -88,7 +75,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { received1 shouldBe List(6, 5, 4, 3, 2, 1) received2 shouldBe List(6, 5, 4, 3, 2) - val cancel3 = owner.unsafeSubscribe() + val cancelX = stream.unsafeSubscribe() mapped shouldBe List(6, 5, 4, 3, 2, 1) received1 shouldBe List(6, 5, 4, 3, 2, 1) @@ -100,19 +87,13 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { received1 shouldBe List(7, 6, 5, 4, 3, 2, 1) received2 shouldBe List(7, 6, 5, 4, 3, 2) - cancel2.unsafeCancel() - - mapped shouldBe List(7, 6, 5, 4, 3, 2, 1) - received1 shouldBe List(7, 6, 5, 4, 3, 2, 1) - received2 shouldBe List(7, 6, 5, 4, 3, 2) - variable.set(8) mapped shouldBe List(8, 7, 6, 5, 4, 3, 2, 1) received1 shouldBe List(8, 7, 6, 5, 4, 3, 2, 1) received2 shouldBe List(8, 7, 6, 5, 4, 3, 2) - cancel3.unsafeCancel() + cancelR2b.unsafeCancel() mapped shouldBe List(8, 7, 6, 5, 4, 3, 2, 1) received1 shouldBe List(8, 7, 6, 5, 4, 3, 2, 1) @@ -120,12 +101,36 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { variable.set(9) - mapped shouldBe List(8, 7, 6, 5, 4, 3, 2, 1) - received1 shouldBe List(8, 7, 6, 5, 4, 3, 2, 1) + mapped shouldBe List(9, 8, 7, 6, 5, 4, 3, 2, 1) + received1 shouldBe List(9, 8, 7, 6, 5, 4, 3, 2, 1) received2 shouldBe List(8, 7, 6, 5, 4, 3, 2) - }).unsafeRunSync() - it should "nested owners" in Owned(SyncIO { + cancelR1b.unsafeCancel() + + mapped shouldBe List(9, 8, 7, 6, 5, 4, 3, 2, 1) + received1 shouldBe List(9, 8, 7, 6, 5, 4, 3, 2, 1) + received2 shouldBe List(8, 7, 6, 5, 4, 3, 2) + + variable.set(10) + + mapped shouldBe List(10, 9, 8, 7, 6, 5, 4, 3, 2, 1) + received1 shouldBe List(9, 8, 7, 6, 5, 4, 3, 2, 1) + received2 shouldBe List(8, 7, 6, 5, 4, 3, 2) + + cancelX.unsafeCancel() + + mapped shouldBe List(10, 9, 8, 7, 6, 5, 4, 3, 2, 1) + received1 shouldBe List(9, 8, 7, 6, 5, 4, 3, 2, 1) + received2 shouldBe List(8, 7, 6, 5, 4, 3, 2) + + variable.set(11) + + mapped shouldBe List(10, 9, 8, 7, 6, 5, 4, 3, 2, 1) + received1 shouldBe List(9, 8, 7, 6, 5, 4, 3, 2, 1) + received2 shouldBe List(8, 7, 6, 5, 4, 3, 2) + } + + it should "nested owners" in { var received1 = List.empty[Int] var innerRx = List.empty[Int] var outerRx = List.empty[Int] @@ -133,7 +138,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { val variable = Var(1) val variable2 = Var(2) - def test(x: Int)(implicit owner: Owner) = Rx { + def test(x: Int) = Rx { innerRx ::= x variable2() * x } @@ -143,13 +148,13 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { outerRx ::= curr val result = test(curr) result() - } + }.unsafeHot() innerRx shouldBe List(1) outerRx shouldBe List(1) received1 shouldBe List.empty - rx.foreach(received1 ::= _) + rx.unsafeForeach(received1 ::= _) innerRx shouldBe List(1) outerRx shouldBe List(1) @@ -178,70 +183,64 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { innerRx shouldBe List(3, 3, 3, 2, 1, 1, 1) // TODO: triggering too often outerRx shouldBe List(3, 3, 2, 1, 1) received1 shouldBe List(12, 9, 6, 3, 2) - }).unsafeRunSync() - - it should "nested owners 2" in Owned - .function(ownedOwner => - SyncIO { - var received1 = List.empty[Int] - var innerRx = List.empty[Int] - var outerRx = List.empty[Int] + } - val variable = Var(1) - val variable2 = Var(2) + it should "nested owners 2" in { + var received1 = List.empty[Int] + var innerRx = List.empty[Int] + var outerRx = List.empty[Int] - implicit val owner: Owner = ownedOwner + val variable = Var(1) + val variable2 = Var(2) - def test(x: Int)(implicit owner: Owner) = Rx { - innerRx ::= x - variable2() * x - } + def test(x: Int) = Rx { + innerRx ::= x + variable2() * x + } - val rx = Rx { - val curr = variable() - outerRx ::= curr - val result = test(curr) - result() - } + val rx = Rx { + val curr = variable() + outerRx ::= curr + val result = test(curr) + result() + } - innerRx shouldBe List(1) - outerRx shouldBe List(1) - received1 shouldBe List.empty + innerRx shouldBe List.empty + outerRx shouldBe List.empty + received1 shouldBe List.empty - rx.foreach(received1 ::= _) + rx.unsafeForeach(received1 ::= _) - innerRx shouldBe List(1) - outerRx shouldBe List(1) - received1 shouldBe List(2) + innerRx shouldBe List(1) + outerRx shouldBe List(1) + received1 shouldBe List(2) - variable2.set(3) + variable2.set(3) - innerRx shouldBe List(1, 1, 1) // TODO: triggering too often - outerRx shouldBe List(1, 1) - received1 shouldBe List(3, 2) + innerRx shouldBe List(1, 1, 1) // TODO: triggering too often + outerRx shouldBe List(1, 1) + received1 shouldBe List(3, 2) - variable.set(2) + variable.set(2) - innerRx shouldBe List(2, 1, 1, 1) - outerRx shouldBe List(2, 1, 1) - received1 shouldBe List(6, 3, 2) + innerRx shouldBe List(2, 1, 1, 1) + outerRx shouldBe List(2, 1, 1) + received1 shouldBe List(6, 3, 2) - variable.set(3) + variable.set(3) - innerRx shouldBe List(3, 2, 1, 1, 1) - outerRx shouldBe List(3, 2, 1, 1) - received1 shouldBe List(9, 6, 3, 2) + innerRx shouldBe List(3, 2, 1, 1, 1) + outerRx shouldBe List(3, 2, 1, 1) + received1 shouldBe List(9, 6, 3, 2) - variable2.set(4) + variable2.set(4) - innerRx shouldBe List(3, 3, 3, 2, 1, 1, 1) // TODO: triggering too often - outerRx shouldBe List(3, 3, 2, 1, 1) - received1 shouldBe List(12, 9, 6, 3, 2) - }, - ) - .unsafeRunSync() + innerRx shouldBe List(3, 3, 3, 2, 1, 1, 1) // TODO: triggering too often + outerRx shouldBe List(3, 3, 2, 1, 1) + received1 shouldBe List(12, 9, 6, 3, 2) + } - it should "sequence with nesting" in Owned(SyncIO { + it should "sequence with nesting" in { var received1 = List.empty[Int] var mapped = List.empty[Boolean] @@ -269,10 +268,10 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { } } - mapped shouldBe List(false) + mapped shouldBe List.empty received1 shouldBe List.empty - stream.foreach(received1 ::= _) + stream.unsafeForeach(received1 ::= _) mapped shouldBe List(false) received1 shouldBe List(-1) @@ -285,20 +284,20 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { variable.set(Some(2)) mapped shouldBe List(true, false) - received1 shouldBe List(1, 12, 2, -1) - }).unsafeRunSync() + received1 shouldBe List(1, 2, -1) + } - it should "be distinct" in Owned(SyncIO { + it should "be distinct" in { var mapped = List.empty[Int] var received1 = List.empty[Boolean] val variable = Var(1) val stream = variable.map { x => mapped ::= x; x % 2 == 0 } - mapped shouldBe List(1) + mapped shouldBe List.empty received1 shouldBe List.empty - stream.foreach(received1 ::= _) + stream.unsafeForeach(received1 ::= _) mapped shouldBe List(1) received1 shouldBe List(false) @@ -322,9 +321,9 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { mapped shouldBe List(5, 4, 2, 1) received1 shouldBe List(false, true, false) - }).unsafeRunSync() + } - it should "work without glitches in chain" in Owned(SyncIO { + it should "work without glitches in chain" in { var liveCounter = 0 var mapped = List.empty[Int] var received1 = List.empty[Boolean] @@ -334,7 +333,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { val stream = variable.map { x => mapped ::= x; x % 2 == 0 } - stream.foreach(received1 ::= _) + stream.unsafeForeach(received1 ::= _) mapped shouldBe List(1) received1 shouldBe List(false) @@ -344,12 +343,12 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { s"${variable()}: ${stream()}" } - rx.foreach(receivedRx ::= _) + rx.unsafeForeach(receivedRx ::= _) mapped shouldBe List(1) received1 shouldBe List(false) receivedRx shouldBe List("1: false") - rx.now() shouldBe "1: false" + rx.nowIfSubscribed() shouldBe "1: false" liveCounter shouldBe 1 variable.set(2) @@ -357,7 +356,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { mapped shouldBe List(2, 1) received1 shouldBe List(true, false) receivedRx shouldBe List("2: true", "1: false") - rx.now() shouldBe "2: true" + rx.nowIfSubscribed() shouldBe "2: true" liveCounter shouldBe 2 variable.set(2) @@ -365,7 +364,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { mapped shouldBe List(2, 1) received1 shouldBe List(true, false) receivedRx shouldBe List("2: true", "1: false") - rx.now() shouldBe "2: true" + rx.nowIfSubscribed() shouldBe "2: true" liveCounter shouldBe 2 variable.set(4) @@ -373,7 +372,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { mapped shouldBe List(4, 2, 1) received1 shouldBe List(true, false) receivedRx shouldBe List("4: true", "2: true", "1: false") - rx.now() shouldBe "4: true" + rx.nowIfSubscribed() shouldBe "4: true" liveCounter shouldBe 3 variable.set(5) @@ -381,11 +380,11 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { mapped shouldBe List(5, 4, 2, 1) received1 shouldBe List(false, true, false) receivedRx shouldBe List("5: false", "4: true", "2: true", "1: false") - rx.now() shouldBe "5: false" + rx.nowIfSubscribed() shouldBe "5: false" liveCounter shouldBe 4 - }).unsafeRunSync() + } - it should "work nested" in Owned(SyncIO { + it should "work nested" in { var liveCounter = 0 var liveCounter2 = 0 @@ -401,32 +400,32 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { } variable() + nested() - } + }.unsafeHot() - rx.now() shouldBe 3 + rx.nowIfSubscribed() shouldBe 3 liveCounter shouldBe 1 liveCounter2 shouldBe 1 variable.set(2) - rx.now() shouldBe 4 + rx.nowIfSubscribed() shouldBe 4 liveCounter shouldBe 2 liveCounter2 shouldBe 2 variable2.set(4) - rx.now() shouldBe 6 + rx.nowIfSubscribed() shouldBe 6 liveCounter shouldBe 3 liveCounter2 shouldBe 4 // TODO: why do we jump to 4 calculations here instead of 3? variable.set(3) - rx.now() shouldBe 7 + rx.nowIfSubscribed() shouldBe 7 liveCounter shouldBe 4 liveCounter2 shouldBe 5 - }).unsafeRunSync() + } - it should "work with now" in Owned(SyncIO { + it should "work with now" in { var liveCounter = 0 val variable = Var(1) @@ -436,92 +435,85 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { val rx = Rx { liveCounter += 1 s"${variable()}, ${variable2()}, ${variable3.now()}" - } + }.unsafeHot() - rx.now() shouldBe "1, 2, 3" + rx.nowIfSubscribed() shouldBe "1, 2, 3" liveCounter shouldBe 1 variable.set(2) - rx.now() shouldBe "2, 2, 3" + rx.nowIfSubscribed() shouldBe "2, 2, 3" liveCounter shouldBe 2 variable.set(2) - rx.now() shouldBe "2, 2, 3" + rx.nowIfSubscribed() shouldBe "2, 2, 3" liveCounter shouldBe 2 variable2.set(10) - rx.now() shouldBe "2, 10, 3" + rx.nowIfSubscribed() shouldBe "2, 10, 3" liveCounter shouldBe 3 variable3.set(5) - rx.now() shouldBe "2, 10, 3" + rx.nowIfSubscribed() shouldBe "2, 10, 3" liveCounter shouldBe 3 variable2.set(100) - rx.now() shouldBe "2, 100, 5" + rx.nowIfSubscribed() shouldBe "2, 100, 5" liveCounter shouldBe 4 - }).unsafeRunSync() + } - it should "work with multi nesting" in Owned(SyncIO { + it should "work with multi nesting" in { var liveCounter = 0 val variable = Var(1) val variable2 = Var(2) val variable3 = Var(3) - val rx = Owned(SyncIO { - Owned(SyncIO { + val rx = Rx { + liveCounter += 1 + Rx { Rx { - liveCounter += 1 - - Owned(SyncIO { - Rx { - Rx { - s"${variable()}, ${variable2()}, ${variable3.now()}" - } - }(implicitly)() - }).unsafeRunSync()() + s"${variable()}, ${variable2()}, ${variable3.now()}" } - }).unsafeRunSync() - }).unsafeRunSync() + }.apply().apply() + }.unsafeHot() - rx.now() shouldBe "1, 2, 3" + rx.nowIfSubscribed() shouldBe "1, 2, 3" liveCounter shouldBe 1 variable.set(2) - rx.now() shouldBe "2, 2, 3" + rx.nowIfSubscribed() shouldBe "2, 2, 3" liveCounter shouldBe 2 variable.set(2) - rx.now() shouldBe "2, 2, 3" + rx.nowIfSubscribed() shouldBe "2, 2, 3" liveCounter shouldBe 2 variable2.set(10) - rx.now() shouldBe "2, 10, 3" + rx.nowIfSubscribed() shouldBe "2, 10, 3" liveCounter shouldBe 3 variable3.set(5) - rx.now() shouldBe "2, 10, 3" + rx.nowIfSubscribed() shouldBe "2, 10, 3" liveCounter shouldBe 3 variable2.set(100) - rx.now() shouldBe "2, 100, 5" + rx.nowIfSubscribed() shouldBe "2, 100, 5" liveCounter shouldBe 4 - }).unsafeRunSync() + } - it should "diamond" in Owned(SyncIO { + it should "diamond" in { var liveCounter = 0 var mapped1 = List.empty[Int] var mapped2 = List.empty[Int] @@ -534,8 +526,8 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { val stream1 = variable.map { x => mapped1 ::= x; x % 2 == 0 } val stream2 = variable.map { x => mapped2 ::= x; x % 2 == 0 } - stream1.foreach(received1 ::= _) - stream2.foreach(received2 ::= _) + stream1.unsafeForeach(received1 ::= _) + stream2.unsafeForeach(received2 ::= _) mapped1 shouldBe List(1) mapped2 shouldBe List(1) @@ -547,14 +539,14 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { s"${stream1()}:${stream2()}" } - rx.foreach(receivedRx ::= _) + rx.unsafeForeach(receivedRx ::= _) mapped1 shouldBe List(1) mapped2 shouldBe List(1) received1 shouldBe List(false) received2 shouldBe List(false) receivedRx shouldBe List("false:false") - rx.now() shouldBe "false:false" + rx.nowIfSubscribed() shouldBe "false:false" liveCounter shouldBe 1 variable.set(2) @@ -564,7 +556,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { received1 shouldBe List(true, false) received2 shouldBe List(true, false) receivedRx shouldBe List("true:true", "true:false", "false:false") // glitch - rx.now() shouldBe "true:true" + rx.nowIfSubscribed() shouldBe "true:true" liveCounter shouldBe 3 variable.set(2) @@ -574,7 +566,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { received1 shouldBe List(true, false) received2 shouldBe List(true, false) receivedRx shouldBe List("true:true", "true:false", "false:false") - rx.now() shouldBe "true:true" + rx.nowIfSubscribed() shouldBe "true:true" liveCounter shouldBe 3 variable.set(4) @@ -584,7 +576,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { received1 shouldBe List(true, false) received2 shouldBe List(true, false) receivedRx shouldBe List("true:true", "true:false", "false:false") - rx.now() shouldBe "true:true" + rx.nowIfSubscribed() shouldBe "true:true" liveCounter shouldBe 3 variable.set(5) @@ -594,16 +586,16 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { received1 shouldBe List(false, true, false) received2 shouldBe List(false, true, false) receivedRx shouldBe List("false:false", "false:true", "true:true", "true:false", "false:false") // glitch - rx.now() shouldBe "false:false" + rx.nowIfSubscribed() shouldBe "false:false" liveCounter shouldBe 5 - }).unsafeRunSync() + } - it should "collect" in Owned(SyncIO { + it should "collect" in { val variable = Var[Option[Int]](Some(1)) val collected = variable.collect { case Some(x) => x }(0) var collectedStates = Vector.empty[Int] - collected.foreach(collectedStates :+= _) + collected.unsafeForeach(collectedStates :+= _) collectedStates shouldBe Vector(1) @@ -613,14 +605,14 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { variable.set(Some(17)) collectedStates shouldBe Vector(1, 17) - }).unsafeRunSync() + } - it should "collect initial none" in Owned(SyncIO { + it should "collect initial none" in { val variable = Var[Option[Int]](None) val collected = variable.collect { case Some(x) => x }(0) var collectedStates = Vector.empty[Int] - collected.foreach(collectedStates :+= _) + collected.unsafeForeach(collectedStates :+= _) collectedStates shouldBe Vector(0) @@ -630,143 +622,147 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { variable.set(Some(17)) collectedStates shouldBe Vector(0, 17) - }).unsafeRunSync() + } - it should "sequence on Var[Seq[T]]" in Owned(SyncIO { + it should "sequence on Var[Seq[T]]" in { { // inner.set on seed value val variable = Var[Seq[Int]](Seq(1)) - val sequence: Rx[Seq[Var[Int]]] = variable.sequence + val sequence: Rx[Seq[Var[Int]]] = variable.sequence.unsafeHot() - variable.now() shouldBe Seq(1) - sequence.now().map(_.now()) shouldBe Seq(1) + variable.nowIfSubscribed() shouldBe Seq(1) + sequence.nowIfSubscribed().map(_.nowIfSubscribed()) shouldBe Seq(1) - sequence.now()(0).set(2) - variable.now() shouldBe Seq(2) + sequence.nowIfSubscribed().apply(0).set(2) + variable.nowIfSubscribed() shouldBe Seq(2) } { // inner.set on value after seed val variable = Var[Seq[Int]](Seq.empty) - val sequence: Rx[Seq[Var[Int]]] = variable.sequence + val sequence: Rx[Seq[Var[Int]]] = variable.sequence.unsafeHot() - variable.now() shouldBe Seq.empty - sequence.now().map(_.now()) shouldBe Seq.empty + variable.nowIfSubscribed() shouldBe Seq.empty + sequence.nowIfSubscribed().map(_.nowIfSubscribed()) shouldBe Seq.empty variable.set(Seq(1)) - sequence.now().map(_.now()) shouldBe Seq(1) + sequence.nowIfSubscribed().map(_.nowIfSubscribed()) shouldBe Seq(1) - sequence.now()(0).set(2) - variable.now() shouldBe Seq(2) + sequence.nowIfSubscribed().apply(0).set(2) + variable.nowIfSubscribed() shouldBe Seq(2) } - }).unsafeRunSync() + } - it should "sequence on Var[Option[T]]" in Owned(SyncIO { + it should "sequence on Var[Option[T]]" in { { // inner.set on seed value val variable = Var[Option[Int]](Some(1)) - val sequence: Rx[Option[Var[Int]]] = variable.sequence + val sequence: Rx[Option[Var[Int]]] = variable.sequence.unsafeHot() - variable.now() shouldBe Some(1) - sequence.now().map(_.now()) shouldBe Some(1) + variable.nowIfSubscribed() shouldBe Some(1) + sequence.nowIfSubscribed().map(_.nowIfSubscribed()) shouldBe Some(1) - sequence.now().get.set(2) - variable.now() shouldBe Some(2) + sequence.nowIfSubscribed().get.set(2) + variable.nowIfSubscribed() shouldBe Some(2) } { // inner.set on value after seed val variable = Var[Option[Int]](Option.empty) - val sequence: Rx[Option[Var[Int]]] = variable.sequence + val sequence: Rx[Option[Var[Int]]] = variable.sequence.unsafeHot() - variable.now() shouldBe None - sequence.now().map(_.now()) shouldBe None + variable.nowIfSubscribed() shouldBe None + sequence.nowIfSubscribed().map(_.nowIfSubscribed()) shouldBe None variable.set(Option(1)) - sequence.now().map(_.now()) shouldBe Option(1) + sequence.nowIfSubscribed().map(_.nowIfSubscribed()) shouldBe Option(1) - sequence.now().get.set(2) - variable.now() shouldBe Option(2) + sequence.nowIfSubscribed().get.set(2) + variable.nowIfSubscribed() shouldBe Option(2) variable.set(None) - sequence.now().map(_.now()) shouldBe None + sequence.nowIfSubscribed().map(_.nowIfSubscribed()) shouldBe None } { // inner.set on seed value val variable = Var[Option[Int]](Some(1)) - val sequence: Rx[Option[Var[Int]]] = variable.sequence + val sequence: Rx[Option[Var[Int]]] = variable.sequence.unsafeHot() var outerTriggered = 0 var innerTriggered = 0 - sequence.foreach(_ => outerTriggered += 1) - sequence.now().foreach(_.foreach(_ => innerTriggered += 1)) + sequence.unsafeForeach(_ => outerTriggered += 1) + sequence.nowIfSubscribed().foreach(_.unsafeForeach(_ => innerTriggered += 1)) - variable.now() shouldBe Some(1) - sequence.now().map(_.now()) shouldBe Some(1) + variable.nowIfSubscribed() shouldBe Some(1) + sequence.nowIfSubscribed().map(_.nowIfSubscribed()) shouldBe Some(1) outerTriggered shouldBe 1 innerTriggered shouldBe 1 - val varRefA = sequence.now().get + val varRefA = sequence.nowIfSubscribed().get variable.set(Some(2)) - variable.now() shouldBe Some(2) - sequence.now().map(_.now()) shouldBe Some(2) + variable.nowIfSubscribed() shouldBe Some(2) + sequence.nowIfSubscribed().map(_.nowIfSubscribed()) shouldBe Some(2) outerTriggered shouldBe 1 innerTriggered shouldBe 2 - val varRefB = sequence.now().get + val varRefB = sequence.nowIfSubscribed().get assert(varRefA eq varRefB) } - }).unsafeRunSync() + } - it should "lens" in Owned(SyncIO { + it should "lens" in { val a: Var[(Int, String)] = Var((0, "Wurst")) val b: Var[String] = a.lens(_._2)((a, b) => a.copy(_2 = b)) val c: Rx[String] = b.map(_ + "q") - a.now() shouldBe ((0, "Wurst")) - b.now() shouldBe "Wurst" - c.now() shouldBe "Wurstq" + a.unsafeSubscribe() + b.unsafeSubscribe() + c.unsafeSubscribe() + + a.nowIfSubscribed() shouldBe ((0, "Wurst")) + b.nowIfSubscribed() shouldBe "Wurst" + c.nowIfSubscribed() shouldBe "Wurstq" a.set((1, "hoho")) - a.now() shouldBe ((1, "hoho")) - b.now() shouldBe "hoho" - c.now() shouldBe "hohoq" + a.nowIfSubscribed() shouldBe ((1, "hoho")) + b.nowIfSubscribed() shouldBe "hoho" + c.nowIfSubscribed() shouldBe "hohoq" b.set("Voodoo") - a.now() shouldBe ((1, "Voodoo")) - b.now() shouldBe "Voodoo" - c.now() shouldBe "Voodooq" + a.nowIfSubscribed() shouldBe ((1, "Voodoo")) + b.nowIfSubscribed() shouldBe "Voodoo" + c.nowIfSubscribed() shouldBe "Voodooq" a.set((3, "genau")) - a.now() shouldBe ((3, "genau")) - b.now() shouldBe "genau" - c.now() shouldBe "genauq" + a.nowIfSubscribed() shouldBe ((3, "genau")) + b.nowIfSubscribed() shouldBe "genau" + c.nowIfSubscribed() shouldBe "genauq" b.set("Schwein") - a.now() shouldBe ((3, "Schwein")) - b.now() shouldBe "Schwein" - c.now() shouldBe "Schweinq" - }).unsafeRunSync() + a.nowIfSubscribed() shouldBe ((3, "Schwein")) + b.nowIfSubscribed() shouldBe "Schwein" + c.nowIfSubscribed() shouldBe "Schweinq" + } it should "lens with monocle" in { case class Company(name: String, zipcode: Int) case class Employee(name: String, company: Company) - Owned(SyncIO { - val employee = Var(Employee("jules", Company("wules", 7))) - val zipcode = employee.lensO(GenLens[Employee](_.company.zipcode)) + val employee = Var(Employee("jules", Company("wules", 7))) + val zipcode = employee.lensO(GenLens[Employee](_.company.zipcode)) + + zipcode.unsafeSubscribe() - employee.now() shouldBe Employee("jules", Company("wules", 7)) - zipcode.now() shouldBe 7 + employee.nowIfSubscribed() shouldBe Employee("jules", Company("wules", 7)) + zipcode.nowIfSubscribed() shouldBe 7 - zipcode.set(8) - employee.now() shouldBe Employee("jules", Company("wules", 8)) - zipcode.now() shouldBe 8 + zipcode.set(8) + employee.nowIfSubscribed() shouldBe Employee("jules", Company("wules", 8)) + zipcode.nowIfSubscribed() shouldBe 8 - employee.set(Employee("gula", Company("bori", 6))) - employee.now() shouldBe Employee("gula", Company("bori", 6)) - zipcode.now() shouldBe 6 - }).unsafeRunSync() + employee.set(Employee("gula", Company("bori", 6))) + employee.nowIfSubscribed() shouldBe Employee("gula", Company("bori", 6)) + zipcode.nowIfSubscribed() shouldBe 6 } it should "optics operations" in { @@ -774,48 +770,200 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { case class EventA(i: Int) extends Event case class EventB(s: String) extends Event - Owned(SyncIO { - val eventVar: Var[Event] = Var[Event](EventA(0)) - val eventNotAVar: Var[Event] = Var[Event](EventB("")) + val eventVar: Var[Event] = Var[Event](EventA(0)) + val eventNotVar: Var[Event] = Var[Event](EventB("")) + + val eventAVar = eventVar.prismO(GenPrism[Event, EventA])(null) + val eventAVar2 = eventVar.subType[EventA](null) + val eventNotAVar = eventNotVar.prismO(GenPrism[Event, EventA])(null) + + eventAVar.unsafeSubscribe() + eventAVar2.unsafeSubscribe() + eventNotAVar.unsafeSubscribe() + + eventVar.nowIfSubscribed() shouldBe EventA(0) + eventAVar.nowIfSubscribed() shouldBe EventA(0) + eventAVar2.nowIfSubscribed() shouldBe EventA(0) + eventNotAVar.nowIfSubscribed() shouldBe null + + eventAVar.set(EventA(1)) + + eventVar.nowIfSubscribed() shouldBe EventA(1) + eventAVar.nowIfSubscribed() shouldBe EventA(1) + eventAVar2.nowIfSubscribed() shouldBe EventA(1) + + eventVar.set(EventB("he")) + + eventVar.nowIfSubscribed() shouldBe EventB("he") + eventAVar.nowIfSubscribed() shouldBe EventA(1) + eventAVar2.nowIfSubscribed() shouldBe EventA(1) + + eventAVar.set(EventA(2)) + + eventVar.nowIfSubscribed() shouldBe EventA(2) + eventAVar.nowIfSubscribed() shouldBe EventA(2) + eventAVar2.nowIfSubscribed() shouldBe EventA(2) + + eventVar.set(EventA(3)) + + eventVar.nowIfSubscribed() shouldBe EventA(3) + eventAVar.nowIfSubscribed() shouldBe EventA(3) + eventAVar2.nowIfSubscribed() shouldBe EventA(3) + } + + it should "map and now()" in { + val variable = Var(1) + val mapped = variable.map(_ + 1) + + variable.nowOption() shouldBe Some(1) + variable.now() shouldBe 1 + variable.nowOption() shouldBe Some(1) + mapped.nowOption() shouldBe None + mapped.now() shouldBe 2 + mapped.nowOption() shouldBe None + + variable.set(2) + + variable.nowOption() shouldBe Some(2) + variable.now() shouldBe 2 + variable.nowOption() shouldBe Some(2) + mapped.nowOption() shouldBe None + mapped.now() shouldBe 3 + mapped.nowOption() shouldBe None + } + + it should "subscribe and now on rx with lazy subscriptions" in { + var triggers1 = List.empty[Int] + var triggerRxCount = 0 + + val variable1 = Var(1) + val variable1Logged = variable1.tap(triggers1 ::= _) + + val mapped = Rx { + triggerRxCount += 1 + variable1Logged() + 1 + } + + val cancelable = mapped.unsafeSubscribe() + + triggers1 shouldBe List(1) + triggerRxCount shouldBe 1 + + mapped.nowOption() shouldBe Some(2) + mapped.now() shouldBe 2 + mapped.nowOption() shouldBe Some(2) + + variable1.set(2) + + triggers1 shouldBe List(2,1) + triggerRxCount shouldBe 2 + + mapped.nowOption() shouldBe Some(3) + mapped.now() shouldBe 3 + mapped.nowOption() shouldBe Some(3) + + cancelable.unsafeCancel() + + mapped.nowOption() shouldBe None + mapped.now() shouldBe 3 + mapped.nowOption() shouldBe None - val eventAVarOption: Option[Var[EventA]] = eventVar.prismO(GenPrism[Event, EventA]) - val eventAVarOption2: Option[Var[EventA]] = eventVar.subType[EventA] - val eventNotAVarOption: Option[Var[EventA]] = eventNotAVar.prismO(GenPrism[Event, EventA]) + triggers1 shouldBe List(2,2,1) + triggerRxCount shouldBe 3 + } + + it should "now() in Rx, and owners with lazy subscriptions" in { + var triggers1 = List.empty[Int] + var triggers2 = List.empty[Int] + var triggerRxCount = 0 + + val variable1 = Var(1) + val variable2 = Var(1) + val variable1Logged = variable1.tap(triggers1 ::= _) + val variable2Logged = variable2.tap(triggers2 ::= _) + + // use mock locally + implicitly[NowOwner].isInstanceOf[LiveOwner] shouldBe false + val mapped = Rx { + // use mock locally + implicitly[NowOwner].isInstanceOf[LiveOwner] shouldBe true + + triggerRxCount += 1 + variable1Logged() + variable2Logged.now() + } + + triggers1 shouldBe List.empty + triggers2 shouldBe List.empty + triggerRxCount shouldBe 0 + + mapped.nowOption() shouldBe None + mapped.now() shouldBe 2 + mapped.nowOption() shouldBe None + + triggers1 shouldBe List(1) + triggers2 shouldBe List(1) + triggerRxCount shouldBe 1 - eventAVarOption.isDefined shouldBe true - eventAVarOption2.isDefined shouldBe true - eventNotAVarOption.isDefined shouldBe false + mapped.nowOption() shouldBe None + mapped.now() shouldBe 2 + mapped.nowOption() shouldBe None - val eventAVar = eventAVarOption.get - val eventAVar2 = eventAVarOption2.get + triggers1 shouldBe List(1,1) + triggers2 shouldBe List(1,1) + triggerRxCount shouldBe 2 - eventVar.now() shouldBe EventA(0) - eventAVar.now() shouldBe EventA(0) - eventAVar2.now() shouldBe EventA(0) + val cancelable = mapped.unsafeSubscribe() + + triggers1 shouldBe List(1,1,1) + triggers2 shouldBe List(1,1,1) + triggerRxCount shouldBe 3 + + mapped.nowOption() shouldBe Some(2) + mapped.now() shouldBe 2 + mapped.nowOption() shouldBe Some(2) + + variable1.set(2) + + triggers1 shouldBe List(2,1,1,1) + triggers2 shouldBe List(1,1,1) + triggerRxCount shouldBe 4 + + mapped.nowOption() shouldBe Some(3) + mapped.now() shouldBe 3 + mapped.nowOption() shouldBe Some(3) + + triggers1 shouldBe List(2,1,1,1) + triggers2 shouldBe List(1,1,1) + triggerRxCount shouldBe 4 + + variable2.set(10) - eventAVar.set(EventA(1)) + mapped.nowOption() shouldBe Some(3) + mapped.now() shouldBe 3 + mapped.nowOption() shouldBe Some(3) - eventVar.now() shouldBe EventA(1) - eventAVar.now() shouldBe EventA(1) - eventAVar2.now() shouldBe EventA(1) + triggers1 shouldBe List(2,1,1,1) + triggers2 shouldBe List(10,1,1,1) + triggerRxCount shouldBe 4 - eventVar.set(EventB("he")) + variable1.set(3) - eventVar.now() shouldBe EventB("he") - eventAVar.now() shouldBe EventA(1) - eventAVar2.now() shouldBe EventA(1) + mapped.nowOption() shouldBe Some(13) + mapped.now() shouldBe 13 + mapped.nowOption() shouldBe Some(13) - eventAVar.set(EventA(2)) + triggers1 shouldBe List(3,2,1,1,1) + triggers2 shouldBe List(10,1,1,1) + triggerRxCount shouldBe 5 - eventVar.now() shouldBe EventA(2) - eventAVar.now() shouldBe EventA(2) - eventAVar2.now() shouldBe EventA(2) + cancelable.unsafeCancel() - eventVar.set(EventA(3)) + mapped.nowOption() shouldBe None + mapped.now() shouldBe 13 + mapped.nowOption() shouldBe None - eventVar.now() shouldBe EventA(3) - eventAVar.now() shouldBe EventA(3) - eventAVar2.now() shouldBe EventA(3) - }).unsafeRunSync() + triggers1 shouldBe List(3,3,2,1,1,1) + triggers2 shouldBe List(10,10,1,1,1) + triggerRxCount shouldBe 6 } } From 7eacf42e6c675dcfead3ce9bd76afecda4b990b3 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 9 Jan 2023 23:22:02 +0100 Subject: [PATCH 02/44] add Var.none/some/subjectSync --- reactive/src/main/scala/colibri/reactive/Reactive.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index caf9cf0c..caee1246 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -178,6 +178,11 @@ trait Var[A] extends RxWriter[A] with Rx[A] { object Var { def apply[A](seed: A): Var[A] = new VarSubject(seed) + def none[A](): Var[Option[A]] = new VarSubject(None) + def some[A](seed: A): Var[Option[A]] = new VarSubject(Some(seed)) + + def subjectSync[A](read: Subject[A]): Var[A] = combine(Rx.observableSync(read), RxWriter.observer(read)) + def combine[A](read: Rx[A], write: RxWriter[A]): Var[A] = new VarCombine(read, write) @inline implicit class SeqVarOperations[A](rxvar: Var[Seq[A]]) { From e1cdcf3de8cb819a67ba41a958215452f226d97c Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 9 Jan 2023 23:33:30 +0100 Subject: [PATCH 03/44] format --- .../scala-3/colibri/reactive/RxPlatform.scala | 2 +- .../main/scala/colibri/reactive/Owner.scala | 7 +-- .../scala/colibri/reactive/Reactive.scala | 41 +++++++++-------- .../src/test/scala/colibri/ReactiveSpec.scala | 46 +++++++++---------- 4 files changed, 49 insertions(+), 47 deletions(-) diff --git a/reactive/src/main/scala-3/colibri/reactive/RxPlatform.scala b/reactive/src/main/scala-3/colibri/reactive/RxPlatform.scala index 1aff9b35..5f97dbed 100644 --- a/reactive/src/main/scala-3/colibri/reactive/RxPlatform.scala +++ b/reactive/src/main/scala-3/colibri/reactive/RxPlatform.scala @@ -1,5 +1,5 @@ package colibri.reactive trait RxPlatform { - def apply[R](f: LiveOwner ?=> R): Rx[R] = Rx.function(implicit owner => f) + def apply[R](f: LiveOwner ?=> R): Rx[R] = Rx.function(implicit owner => f) } diff --git a/reactive/src/main/scala/colibri/reactive/Owner.scala b/reactive/src/main/scala/colibri/reactive/Owner.scala index ed9b1d32..cd9c499a 100644 --- a/reactive/src/main/scala/colibri/reactive/Owner.scala +++ b/reactive/src/main/scala/colibri/reactive/Owner.scala @@ -2,14 +2,15 @@ package colibri.reactive import colibri._ -trait NowOwner { +trait NowOwner { def unsafeNow[A](rx: Rx[A]): A } object NowOwner { implicit object global extends NowOwner { def unsafeNow[A](rx: Rx[A]): A = { val cancelable = rx.unsafeSubscribe() - try(rx.nowIfSubscribed()) finally(cancelable.unsafeCancel()) + try (rx.nowIfSubscribed()) + finally (cancelable.unsafeCancel()) } } } @@ -17,7 +18,7 @@ object NowOwner { @annotation.implicitNotFound( "No implicit LiveOwner is available here! Wrap inside `Rx { }`, or provide an implicit `LiveOwner`.", ) -trait LiveOwner extends NowOwner { +trait LiveOwner extends NowOwner { def cancelable: Cancelable def liveObservable: Observable[Any] diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index caee1246..fbd3721e 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -23,10 +23,10 @@ trait Rx[+A] { final def nowIfSubscribed(): A = nowOption().getOrElse(throw RxMissingNowException) final def collect[B](f: PartialFunction[A, B])(seed: => B): Rx[B] = transformRx(_.collect(f))(seed) - final def map[B](f: A => B): Rx[B] = transformRxSync(_.map(f)) - final def mapEither[B](f: A => Either[Throwable, B]): Rx[B] = transformRxSync(_.mapEither(f)) - final def tap(f: A => Unit): Rx[A] = transformRxSync(_.tap(f)) - final def tapLater(f: A => Unit): Rx[A] = transformRxSync(_.tail.tap(f)) + final def map[B](f: A => B): Rx[B] = transformRxSync(_.map(f)) + final def mapEither[B](f: A => Either[Throwable, B]): Rx[B] = transformRxSync(_.mapEither(f)) + final def tap(f: A => Unit): Rx[A] = transformRxSync(_.tap(f)) + final def tapLater(f: A => Unit): Rx[A] = transformRxSync(_.tail.tap(f)) final def mapSyncEffect[F[_]: RunSyncEffect, B](f: A => F[B]): Rx[B] = transformRxSync(_.mapEffect(f)) final def mapEffect[F[_]: RunEffect, B](f: A => F[B])(seed: => B): Rx[B] = transformRx(_.mapEffect(f))(seed) @@ -47,7 +47,7 @@ trait Rx[+A] { final def transformRx[B](f: Observable[A] => Observable[B])(seed: => B): Rx[B] = Rx.observable(f(observable))(seed) final def transformRxSync[B](f: Observable[A] => Observable[B]): Rx[B] = Rx.observableSync(f(observable)) - final def hot: SyncIO[Rx[A]] = SyncIO(unsafeHot()) + final def hot: SyncIO[Rx[A]] = SyncIO(unsafeHot()) final def unsafeHot(): Rx[A] = { val _ = unsafeSubscribe() @@ -151,7 +151,7 @@ object RxWriter { trait Var[A] extends RxWriter[A] with Rx[A] { final def updateIfSubscribed(f: A => A): Unit = set(f(nowIfSubscribed())) - final def update(f: A => A): Unit = set(f(now())) + final def update(f: A => A): Unit = set(f(now())) final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: Rx[A] => Rx[A2]): Var[A2] = Var.combine(g(this), f(this)) final def transformVarRx(g: Rx[A] => Rx[A]): Var[A] = Var.combine(g(this), this) @@ -169,8 +169,8 @@ trait Var[A] extends RxWriter[A] with Rx[A] { case _ => None }(seed) - final def imapO[B](optic: Iso[A, B]): Var[B] = imap(optic.reverseGet(_))(optic.get(_)) - final def lensO[B](optic: Lens[A, B]): Var[B] = lens(optic.get(_))((base, zoomed) => optic.replace(zoomed)(base)) + final def imapO[B](optic: Iso[A, B]): Var[B] = imap(optic.reverseGet(_))(optic.get(_)) + final def lensO[B](optic: Lens[A, B]): Var[B] = lens(optic.get(_))((base, zoomed) => optic.replace(zoomed)(base)) final def prismO[B](optic: Prism[A, B])(seed: => B): Var[B] = prism(optic.reverseGet(_))(optic.getOption(_))(seed) } @@ -178,7 +178,7 @@ trait Var[A] extends RxWriter[A] with Rx[A] { object Var { def apply[A](seed: A): Var[A] = new VarSubject(seed) - def none[A](): Var[Option[A]] = new VarSubject(None) + def none[A](): Var[Option[A]] = new VarSubject(None) def some[A](seed: A): Var[Option[A]] = new VarSubject(Some(seed)) def subjectSync[A](read: Subject[A]): Var[A] = combine(Rx.observableSync(read), RxWriter.observer(read)) @@ -250,19 +250,20 @@ object Var { } private final class RxConst[A](value: A) extends Rx[A] { - val observable: Observable[A] = Observable.pure(value) - def nowOption(): Option[A] = Some(value) - def now()(implicit owner: NowOwner): A = value + val observable: Observable[A] = Observable.pure(value) + def nowOption(): Option[A] = Some(value) + def now()(implicit owner: NowOwner): A = value def apply()(implicit owner: LiveOwner): A = value } private final class RxObservableSync[A](inner: Observable[A]) extends Rx[A] { private val state = new ReplayLatestSubject[A]() - val observable: Observable[A] = inner.dropUntilSyncLatest.distinctOnEquals.tapCancel(state.unsafeResetState).multicast(state).refCount.distinctOnEquals + val observable: Observable[A] = + inner.dropUntilSyncLatest.distinctOnEquals.tapCancel(state.unsafeResetState).multicast(state).refCount.distinctOnEquals - def nowOption() = state.now() - def now()(implicit owner: NowOwner) = owner.unsafeNow(this) + def nowOption() = state.now() + def now()(implicit owner: NowOwner) = owner.unsafeNow(this) def apply()(implicit owner: LiveOwner): A = owner.unsafeLive(this) } @@ -274,14 +275,14 @@ private final class VarSubject[A](seed: A) ext val observable: Observable[A] = state.distinctOnEquals val observer: Observer[A] = state - def nowOption() = Some(state.now()) - def now()(implicit owner: NowOwner) = state.now() + def nowOption() = Some(state.now()) + def now()(implicit owner: NowOwner) = state.now() def apply()(implicit owner: LiveOwner): A = owner.unsafeLive(this) } private final class VarCombine[A](innerRead: Rx[A], innerWrite: RxWriter[A]) extends Var[A] { - def nowOption() = innerRead.nowOption() + def nowOption() = innerRead.nowOption() def now()(implicit owner: NowOwner) = innerRead.now() def apply()(implicit owner: LiveOwner): A = innerRead() - val observable = innerRead.observable - val observer = innerWrite.observer + val observable = innerRead.observable + val observer = innerWrite.observer } diff --git a/reactive/src/test/scala/colibri/ReactiveSpec.scala b/reactive/src/test/scala/colibri/ReactiveSpec.scala index 54be3f47..1128c720 100644 --- a/reactive/src/test/scala/colibri/ReactiveSpec.scala +++ b/reactive/src/test/scala/colibri/ReactiveSpec.scala @@ -813,7 +813,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { it should "map and now()" in { val variable = Var(1) - val mapped = variable.map(_ + 1) + val mapped = variable.map(_ + 1) variable.nowOption() shouldBe Some(1) variable.now() shouldBe 1 @@ -833,10 +833,10 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { } it should "subscribe and now on rx with lazy subscriptions" in { - var triggers1 = List.empty[Int] + var triggers1 = List.empty[Int] var triggerRxCount = 0 - val variable1 = Var(1) + val variable1 = Var(1) val variable1Logged = variable1.tap(triggers1 ::= _) val mapped = Rx { @@ -855,7 +855,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { variable1.set(2) - triggers1 shouldBe List(2,1) + triggers1 shouldBe List(2, 1) triggerRxCount shouldBe 2 mapped.nowOption() shouldBe Some(3) @@ -868,17 +868,17 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { mapped.now() shouldBe 3 mapped.nowOption() shouldBe None - triggers1 shouldBe List(2,2,1) + triggers1 shouldBe List(2, 2, 1) triggerRxCount shouldBe 3 } it should "now() in Rx, and owners with lazy subscriptions" in { - var triggers1 = List.empty[Int] - var triggers2 = List.empty[Int] + var triggers1 = List.empty[Int] + var triggers2 = List.empty[Int] var triggerRxCount = 0 - val variable1 = Var(1) - val variable2 = Var(1) + val variable1 = Var(1) + val variable2 = Var(1) val variable1Logged = variable1.tap(triggers1 ::= _) val variable2Logged = variable2.tap(triggers2 ::= _) @@ -908,14 +908,14 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { mapped.now() shouldBe 2 mapped.nowOption() shouldBe None - triggers1 shouldBe List(1,1) - triggers2 shouldBe List(1,1) + triggers1 shouldBe List(1, 1) + triggers2 shouldBe List(1, 1) triggerRxCount shouldBe 2 val cancelable = mapped.unsafeSubscribe() - triggers1 shouldBe List(1,1,1) - triggers2 shouldBe List(1,1,1) + triggers1 shouldBe List(1, 1, 1) + triggers2 shouldBe List(1, 1, 1) triggerRxCount shouldBe 3 mapped.nowOption() shouldBe Some(2) @@ -924,16 +924,16 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { variable1.set(2) - triggers1 shouldBe List(2,1,1,1) - triggers2 shouldBe List(1,1,1) + triggers1 shouldBe List(2, 1, 1, 1) + triggers2 shouldBe List(1, 1, 1) triggerRxCount shouldBe 4 mapped.nowOption() shouldBe Some(3) mapped.now() shouldBe 3 mapped.nowOption() shouldBe Some(3) - triggers1 shouldBe List(2,1,1,1) - triggers2 shouldBe List(1,1,1) + triggers1 shouldBe List(2, 1, 1, 1) + triggers2 shouldBe List(1, 1, 1) triggerRxCount shouldBe 4 variable2.set(10) @@ -942,8 +942,8 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { mapped.now() shouldBe 3 mapped.nowOption() shouldBe Some(3) - triggers1 shouldBe List(2,1,1,1) - triggers2 shouldBe List(10,1,1,1) + triggers1 shouldBe List(2, 1, 1, 1) + triggers2 shouldBe List(10, 1, 1, 1) triggerRxCount shouldBe 4 variable1.set(3) @@ -952,8 +952,8 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { mapped.now() shouldBe 13 mapped.nowOption() shouldBe Some(13) - triggers1 shouldBe List(3,2,1,1,1) - triggers2 shouldBe List(10,1,1,1) + triggers1 shouldBe List(3, 2, 1, 1, 1) + triggers2 shouldBe List(10, 1, 1, 1) triggerRxCount shouldBe 5 cancelable.unsafeCancel() @@ -962,8 +962,8 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { mapped.now() shouldBe 13 mapped.nowOption() shouldBe None - triggers1 shouldBe List(3,3,2,1,1,1) - triggers2 shouldBe List(10,10,1,1,1) + triggers1 shouldBe List(3, 3, 2, 1, 1, 1) + triggers2 shouldBe List(10, 10, 1, 1, 1) triggerRxCount shouldBe 6 } } From 87b2dcba7b6d8145830e13c8e6f28f4ced190994 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Tue, 10 Jan 2023 00:58:29 +0100 Subject: [PATCH 04/44] update readme about reactive variables --- README.md | 81 ++++++++++++++++++++++--------------------------------- 1 file changed, 32 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 489a86cc..64e2f60d 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,16 @@ This library includes: Reactive core library with typeclasses: ```scala -libraryDependencies += "com.github.cornerman" %%% "colibri" % "0.5.0" +libraryDependencies += "com.github.cornerman" %%% "colibri" % "0.8.0" ``` ```scala import colibri._ ``` -Reactive variables with hot distinct observables (a bit like scala-rx): +Reactive variables with lazy, distinct, shared state variables (a bit like scala-rx): ```scala -libraryDependencies += "com.github.cornerman" %%% "colibri-reactive" % "0.5.0" +libraryDependencies += "com.github.cornerman" %%% "colibri-reactive" % "0.8.0" ``` ```scala @@ -32,7 +32,7 @@ import colibri.reactive._ For jsdom-based operations in the browser (`EventObservable`, `Storage`): ```scala -libraryDependencies += "com.github.cornerman" %%% "colibri-jsdom" % "0.5.0" +libraryDependencies += "com.github.cornerman" %%% "colibri-jsdom" % "0.8.0" ``` ```scala @@ -41,7 +41,7 @@ import colibri.jsdom._ For scala.rx support (only Scala 2.x): ```scala -libraryDependencies += "com.github.cornerman" %%% "colibri-rx" % "0.5.0" +libraryDependencies += "com.github.cornerman" %%% "colibri-rx" % "0.8.0" ``` ```scala @@ -50,7 +50,7 @@ import colibri.ext.rx._ For airstream support: ```scala -libraryDependencies += "com.github.cornerman" %%% "colibri-airstream" % "0.5.0" +libraryDependencies += "com.github.cornerman" %%% "colibri-airstream" % "0.8.0" ``` ```scala @@ -59,7 +59,7 @@ import colibri.ext.airstream._ For zio support: ```scala -libraryDependencies += "com.github.cornerman" %%% "colibri-zio" % "0.5.0" +libraryDependencies += "com.github.cornerman" %%% "colibri-zio" % "0.8.0" ``` ```scala @@ -68,7 +68,7 @@ import colibri.ext.zio._ For fs2 support (`Source` only): ```scala -libraryDependencies += "com.github.cornerman" %%% "colibri-fs2" % "0.5.0" +libraryDependencies += "com.github.cornerman" %%% "colibri-fs2" % "0.8.0" ``` ```scala @@ -146,9 +146,15 @@ You can convert any `Source` into an `Observable` with `Observable.lift(source)` ## Reactive variables -The module `colibri-reactive` exposes reactive variables. This is hot, distinct observables that always have a value. These reactive variables are meant for managing state - opposed to managing events which is a perfect fit for lazy `Observable` in the core `colibri` library. +The module `colibri-reactive` exposes reactive variables. This is lazy, distinct shared state variables (internally using observables) that always have a value. These reactive variables are meant for managing state - opposed to managing events which is a perfect fit for lazy `Observable` in the core `colibri` library. -This module behaves very similar to scala-rx - just built on top of colibri Observables for seamless integration and powerful operators. It is not entirely glitch-free because invalid state can appear in operators like map or foreach, but you always have a consistent state in `now()` and it reduces the number of intermediate triggers or glitches. You can become completely glitch-free by converting back to observable and using `dropSyncGlitches` which will introduce an async boundary (micro-task). +This module behaves similar to scala-rx - though variables are not hot and it is built on top of colibri Observables for seamless integration and powerful operators. + +The whole thing is not entirely glitch-free, as invalid state can appear in operators like map or foreach. But you always have a consistent state in `now()` and it reduces the number of intermediate triggers or glitches. You can become completely glitch-free by converting back to observable and using `dropSyncGlitches` which will introduce an async boundary (micro-task). + +A state variable is of type `Var[A] extends Rx[A] with RxWriter[A]`. + +The laziness of variables means that the current value is only tracked if anyone subscribes to the `Rx[A]`. So an Rx does not compute anything on its own. You can still always call `now()` on it - if it is currently not subscribed, it will lazily calculate the current value. Example: @@ -156,8 +162,6 @@ Example: import colibri.reactive._ -import colibri.owner.unsafeImplicits._ // dangerous. This never cancels subscriptions. See below! - val variable = Var(1) val variable2 = Var("Test") @@ -165,60 +169,39 @@ val rx = Rx { s"${variable()} - ${variable2()}" } -rx.foreach(println(_)) +val cancelable = rx.unsafeForeach(println(_)) println(variable.now()) // 1 println(variable2.now()) // "Test" println(rx.now()) // "1 - Test" -variable.set(2) +variable.set(2) // println("2 - Test") println(variable.now()) // 2 println(variable2.now()) // "Test" println(rx.now()) // "2 - Test" -variable2.set("Foo") +variable2.set("Foo") // println("2 - Foo") println(variable.now()) // 2 println(variable2.now()) // "Foo" println(rx.now()) // "2 - Foo" -``` - -If you want to work with reactive variables (hot observable), then someone need to cleanup the subscriptions. We call this concept an `Owner`. We use an *unsafe* owner in the above example. It actually never cleans up. It should only ever be used in your main method or for global state. - -You can even work without ever using the unsafe owner or having to pass it implictly. You can use `Owned` blocks instead. Inside an `Owned` block, you will have to return a type that has a `SubscriptionOwner` instance. Example: - -```scala - -import colibri._ -import colibri.reactive._ -import cats.effect.SyncIO - -sealed trait Modifier -object Modifier { - case class ReactiveModifier(rx: Rx[String]) extends Modifier - case class SubscriptionModifier(subscription: () => Cancelable) extends Modifier - case class CombineModifier(modifierA: Modifier, modifierB: Modifier) extends Modifier +cancelable.unsafeCancel() - implicit object subcriptionOwner extends SubscriptionOwner[Modifier] { - def own(owner: Modifier)(subscription: () => Cancelable): Modifier = CombineModifier(owner, SubscriptionModifier(subscription)) - } -} - -val component: SyncIO[Modifier] = Owned { - val variable = Var(1) - val mapped = rx.map(_ + 1) +println(variable.now()) // 2 +println(variable2.now()) // "Foo" +println(rx.now()) // "2 - Foo" - val rx = Rx { - "Hallo: ${mapped()}" - } +variable.set(3) // no println - ReactiveModifier(rx) -} +// now calculates new value lazily +println(variable.now()) // 3 +println(variable2.now()) // "Foo" +println(rx.now()) // "3 - Foo" ``` -For example, [Outwatch](https://github.com/outwatch/outwatch) supports `Owned`: +[Outwatch](https://github.com/outwatch/outwatch) works perfectly with Rx as just like Observable. ```scala @@ -227,7 +210,7 @@ import outwatch.dsl._ import colibri.reactive._ import cats.effect.SyncIO -val component: SyncIO[VModifier] = Owned { +val component: VModifier = { val variable = Var(1) val mapped = rx.map(_ + 1) @@ -241,9 +224,9 @@ val component: SyncIO[VModifier] = Owned { ### Memory management -Every subscription that is created inside of colibri-reactive methods is owned by an implicit `Owner`. For example `map` or `foreach` take an implicit `Owner`. As long as the `Owner` is cancelled when it is not needed anymore, all subscriptions will be cleaned up. The exception is the `Owner.unsafeGlobal` that never cleans up and is meant for global state. +The same principles as for Observables hold. Any cancelable that is returned from the API needs to be handled by the the caller. Best practice: use subscribe/foreach as seldomly as possible - only in selected spots or within a library. -If you are working with `Outwatch`, you can just use `Owned`-blocks returning `VModifier` and everything is handled automatically for you. No memory leaks. +If you are working with `Outwatch`, you can just use `Rx` without ever subscribing yourself. Then all memory management is handled for you automatically. No memory leaks. ## Information From bb812b5602338e23b69877eb0a8bf0331dc23208 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Tue, 10 Jan 2023 12:30:28 +0100 Subject: [PATCH 05/44] remove Rx#scan without seed --- reactive/src/main/scala/colibri/reactive/Reactive.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index fbd3721e..6c7cd7a2 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -94,8 +94,6 @@ object Rx extends RxPlatform { def observableSync[A](observable: Observable[A]): Rx[A] = new RxObservableSync(observable) @inline implicit final class RxOps[A](private val self: Rx[A]) extends AnyVal { - def scan(f: (A, A) => A): Rx[A] = scan(self.nowIfSubscribed())(f) - def scan[B](seed: B)(f: (B, A) => B): Rx[B] = self.transformRxSync(_.scan0(seed)(f)) def filter(f: A => Boolean)(seed: => A): Rx[A] = self.transformRx(_.filter(f))(seed) From 457d1f32cc3b01d0fcf86d01eb3a32fce717dd11 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Tue, 10 Jan 2023 12:35:52 +0100 Subject: [PATCH 06/44] fix tapLater --- .../scala/colibri/reactive/Reactive.scala | 2 +- .../src/test/scala/colibri/ReactiveSpec.scala | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 6c7cd7a2..e3f0015b 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -26,7 +26,7 @@ trait Rx[+A] { final def map[B](f: A => B): Rx[B] = transformRxSync(_.map(f)) final def mapEither[B](f: A => Either[Throwable, B]): Rx[B] = transformRxSync(_.mapEither(f)) final def tap(f: A => Unit): Rx[A] = transformRxSync(_.tap(f)) - final def tapLater(f: A => Unit): Rx[A] = transformRxSync(_.tail.tap(f)) + final def tapLater(f: A => Unit): Rx[A] = transformRx(_.tail.tap(f))(nowIfSubscribed()) final def mapSyncEffect[F[_]: RunSyncEffect, B](f: A => F[B]): Rx[B] = transformRxSync(_.mapEffect(f)) final def mapEffect[F[_]: RunEffect, B](f: A => F[B])(seed: => B): Rx[B] = transformRx(_.mapEffect(f))(seed) diff --git a/reactive/src/test/scala/colibri/ReactiveSpec.scala b/reactive/src/test/scala/colibri/ReactiveSpec.scala index 1128c720..23003aee 100644 --- a/reactive/src/test/scala/colibri/ReactiveSpec.scala +++ b/reactive/src/test/scala/colibri/ReactiveSpec.scala @@ -832,6 +832,54 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { mapped.nowOption() shouldBe None } + it should "tapLater" in { + var triggers1 = List.empty[Int] + val variable1 = Var(1) + val variable1Logged = variable1.tapLater(triggers1 ::= _) + + triggers1 shouldBe List.empty + variable1Logged.nowOption() shouldBe None + triggers1 shouldBe List.empty + + val cancelable = variable1Logged.unsafeSubscribe() + + triggers1 shouldBe List.empty + variable1Logged.nowOption() shouldBe Some(1) + triggers1 shouldBe List.empty + + variable1.set(2) + + triggers1 shouldBe List(2) + variable1Logged.nowOption() shouldBe Some(2) + triggers1 shouldBe List(2) + + variable1.set(3) + + triggers1 shouldBe List(3,2) + variable1Logged.nowOption() shouldBe Some(3) + triggers1 shouldBe List(3,2) + + cancelable.unsafeCancel() + + variable1.set(4) + + triggers1 shouldBe List(3,2) + variable1Logged.nowOption() shouldBe None + triggers1 shouldBe List(3,2) + + variable1Logged.now() shouldBe 4 + triggers1 shouldBe List(3,2) + + variable1.set(5) + + triggers1 shouldBe List(3,2) + variable1Logged.nowOption() shouldBe None + triggers1 shouldBe List(3,2) + + variable1Logged.now() shouldBe 5 + triggers1 shouldBe List(3,2) + } + it should "subscribe and now on rx with lazy subscriptions" in { var triggers1 = List.empty[Int] var triggerRxCount = 0 From 58024276b8bf6c7d892dd26131b1348f769bcf67 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Tue, 10 Jan 2023 12:42:18 +0100 Subject: [PATCH 07/44] Revert "remove Rx#scan without seed" This reverts commit 71f64f6f75cec60049772f67cf8caa2823d04698. --- reactive/src/main/scala/colibri/reactive/Reactive.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index e3f0015b..7a548aac 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -94,6 +94,8 @@ object Rx extends RxPlatform { def observableSync[A](observable: Observable[A]): Rx[A] = new RxObservableSync(observable) @inline implicit final class RxOps[A](private val self: Rx[A]) extends AnyVal { + def scan(f: (A, A) => A): Rx[A] = scan(self.nowIfSubscribed())(f) + def scan[B](seed: B)(f: (B, A) => B): Rx[B] = self.transformRxSync(_.scan0(seed)(f)) def filter(f: A => Boolean)(seed: => A): Rx[A] = self.transformRx(_.filter(f))(seed) From 788bda43701a297abf6c996d9680cb444ee39db2 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Fri, 13 Jan 2023 22:59:25 +0100 Subject: [PATCH 08/44] add RxEvent and VarEvent for (mostly) shared event streams --- .../src/main/scala/colibri/Observable.scala | 4 +- .../scala/colibri/reactive/Reactive.scala | 144 +++++++++++++++--- 2 files changed, 128 insertions(+), 20 deletions(-) diff --git a/colibri/src/main/scala/colibri/Observable.scala b/colibri/src/main/scala/colibri/Observable.scala index 3c3a3773..2dbea529 100644 --- a/colibri/src/main/scala/colibri/Observable.scala +++ b/colibri/src/main/scala/colibri/Observable.scala @@ -512,9 +512,9 @@ object Observable { @deprecated("Use scan0 instead", "0.7.8") def scan0ToList: Observable[List[A]] = scan0(List.empty[A])((list, x) => x :: list) - def scan0[B](seed: B)(f: (B, A) => B): Observable[B] = scan(seed)(f).prepend(seed) + def scan0[B](seed: => B)(f: (B, A) => B): Observable[B] = scan(seed)(f).prependEval(seed) - def scan[B](seed: B)(f: (B, A) => B): Observable[B] = new Observable[B] { + def scan[B](seed: => B)(f: (B, A) => B): Observable[B] = new Observable[B] { def unsafeSubscribe(sink: Observer[B]): Cancelable = source.unsafeSubscribe(sink.contrascan(seed)(f)) } diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 7a548aac..ce03875f 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -13,8 +13,101 @@ import scala.util.control.NonFatal object RxMissingNowException extends Exception("Missing current value inside an Rx. Make sure, the Rx has active subscriptions when calling nowIfSubscribed.") -trait Rx[+A] { +trait RxSource[+A] { def observable: Observable[A] + + final def subscribe: SyncIO[Cancelable] = observable.subscribeSyncIO + + final def unsafeSubscribe(): Cancelable = observable.unsafeSubscribe() + final def unsafeSubscribe(writer: RxWriter[A]): Cancelable = observable.unsafeSubscribe(writer.observer) + + final def unsafeForeach(f: A => Unit): Cancelable = observable.unsafeForeach(f) + final def unsafeForeachLater(f: A => Unit): Cancelable = observable.tail.unsafeForeach(f) +} + +object RxSource { + implicit object source extends Source[RxSource] { + def unsafeSubscribe[A](source: RxSource[A])(sink: Observer[A]): Cancelable = source.observable.unsafeSubscribe(sink) + } +} + +trait RxEvent[+A] extends RxSource[A] { + final def collect[B](f: PartialFunction[A, B]): RxEvent[B] = transformRxEvent(_.collect(f)) + final def map[B](f: A => B): RxEvent[B] = transformRxEvent(_.map(f)) + final def mapEither[B](f: A => Either[Throwable, B]): RxEvent[B] = transformRxEvent(_.mapEither(f)) + final def tap(f: A => Unit): RxEvent[A] = transformRxEvent(_.tap(f)) + final def tapLater(f: A => Unit): RxEvent[A] = transformRxEvent(obs => Observable.concatEffect(obs.headIO, obs.tail.tap(f))) + + final def mapEffect[F[_]: RunEffect, B](f: A => F[B]): RxEvent[B] = transformRxEvent(_.mapEffect(f)) + final def mapFuture[B](f: A => Future[B]): RxEvent[B] = transformRxEvent(_.mapFuture(f)) + + final def as[B](value: B): RxEvent[B] = transformRxEvent(_.as(value)) + final def asEval[B](value: => B): RxEvent[B] = transformRxEvent(_.asEval(value)) + + final def asEffect[F[_]: RunEffect, B](value: F[B]): RxEvent[B] = transformRxEvent(_.asEffect(value)) + final def asFuture[B](value: => Future[B]): RxEvent[B] = transformRxEvent(_.asFuture(value)) + + final def via(writer: RxWriter[A]): RxEvent[A] = transformRxEvent(_.via(writer.observer)) + + final def switchMap[B](f: A => RxEvent[B]): RxEvent[B] = transformRxEvent(_.switchMap(f andThen (_.observable))) + final def mergeMap[B](f: A => RxEvent[B]): RxEvent[B] = transformRxEvent(_.mergeMap(f andThen (_.observable))) + final def concatMap[B](f: A => RxEvent[B]): RxEvent[B] = transformRxEvent(_.concatMap(f andThen (_.observable))) + + final def combineLatestMap[B, R](sourceB: RxEvent[B])(f: (A, B) => R): RxEvent[R] = transformRxEvent(_.combineLatestMap(sourceB.observable)(f)) + final def combineLatest[B](sourceB: RxEvent[B]): RxEvent[(A,B)] = transformRxEvent(_.combineLatest(sourceB.observable)) + + final def withLatestMap[B, R](sourceB: RxEvent[B])(f: (A, B) => R): RxEvent[R] = transformRxEvent(_.withLatestMap(sourceB.observable)(f)) + final def withLatest[B](sourceB: RxEvent[B]): RxEvent[(A,B)] = transformRxEvent(_.withLatest(sourceB.observable)) + + final def transformRxEvent[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) + final def transformRxEventUnshared[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observableUnshared(f(observable)) + + final def hot: SyncIO[RxEvent[A]] = SyncIO(unsafeHot()) + + final def unsafeHot(): RxEvent[A] = { + val _ = unsafeSubscribe() + this + } +} + +object RxEvent extends RxPlatform { + def empty[A]: RxEvent[A] = RxEventEmpty + + def const[A](value: A): RxEvent[A] = observableUnshared(Observable.pure(value)) + + def iterable[A](value: Iterable[A]): RxEvent[A] = observableUnshared(Observable.fromIterable(value)) + + def effect[F[_]: RunEffect, A](value: F[A]): RxEvent[A] = observable(Observable.fromEffect(value)) + def future[A](value: => Future[A]): RxEvent[A] = observable(Observable.fromFuture(value)) + + def merge[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.mergeIterable(rxs.map(_.observable))) + def switch[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.switchIterable(rxs.map(_.observable))) + def concat[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.concatIterable(rxs.map(_.observable))) + + def observable[A](observable: Observable[A]): RxEvent[A] = new RxEventObservable(observable.publish.refCount) + def observableUnshared[A](observable: Observable[A]): RxEvent[A] = new RxEventObservable(observable) + + @inline implicit final class RxEventOps[A](private val self: RxEvent[A]) extends AnyVal { + def scan[B](seed: => B)(f: (B, A) => B): RxEvent[B] = self.transformRxEvent(_.scan(seed)(f)) + + def filter(f: A => Boolean): RxEvent[A] = self.transformRxEvent(_.filter(f)) + + def prepend(value: A): RxEvent[A] = self.transformRxEvent(_.prepend(value)) + } + + @inline implicit class RxEventBooleanOps(private val source: RxEvent[Boolean]) extends AnyVal { + @inline def toggle[A](ifTrue: => A, ifFalse: => A): RxEvent[A] = source.map { + case true => ifTrue + case false => ifFalse + } + + @inline def toggle[A: Monoid](ifTrue: => A): RxEvent[A] = toggle(ifTrue, Monoid[A].empty) + + @inline def negated: RxEvent[Boolean] = source.map(x => !x) + } +} + +trait Rx[+A] extends RxSource[A] { def nowOption(): Option[A] def apply()(implicit owner: LiveOwner): A @@ -53,14 +146,6 @@ trait Rx[+A] { val _ = unsafeSubscribe() this } - - final def subscribe: SyncIO[Cancelable] = observable.subscribeSyncIO - - final def unsafeSubscribe(): Cancelable = observable.unsafeSubscribe() - final def unsafeSubscribe(writer: RxWriter[A]): Cancelable = observable.unsafeSubscribe(writer.observer) - - final def unsafeForeach(f: A => Unit): Cancelable = observable.unsafeForeach(f) - final def unsafeForeachLater(f: A => Unit): Cancelable = observable.tail.unsafeForeach(f) } object Rx extends RxPlatform { @@ -94,9 +179,9 @@ object Rx extends RxPlatform { def observableSync[A](observable: Observable[A]): Rx[A] = new RxObservableSync(observable) @inline implicit final class RxOps[A](private val self: Rx[A]) extends AnyVal { - def scan(f: (A, A) => A): Rx[A] = scan(self.nowIfSubscribed())(f) + def scanReduce(f: (A, A) => A): Rx[A] = scan(self.nowIfSubscribed())(f) - def scan[B](seed: B)(f: (B, A) => B): Rx[B] = self.transformRxSync(_.scan0(seed)(f)) + def scan[B](seed: => B)(f: (B, A) => B): Rx[B] = self.transformRxSync(_.scan0(seed)(f)) def filter(f: A => Boolean)(seed: => A): Rx[A] = self.transformRx(_.filter(f))(seed) } @@ -111,10 +196,6 @@ object Rx extends RxPlatform { @inline def negated: Rx[Boolean] = source.map(x => !x) } - - implicit object source extends Source[Rx] { - def unsafeSubscribe[A](source: Rx[A])(sink: Observer[A]): Cancelable = source.observable.unsafeSubscribe(sink) - } } trait RxWriter[-A] { @@ -149,6 +230,21 @@ object RxWriter { } } +trait VarEvent[A] extends RxWriter[A] with RxEvent[A] { + final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = VarEvent.combine(g(this), f(this)) + final def transformVarRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.combine(g(this), this) + final def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.combine(this, f(this)) +} + +object VarEvent { + def apply[A](): VarEvent[A] = new VarEventSubject(Nil) + def apply[A](seed: A): VarEvent[A] = new VarEventSubject(seed :: Nil) + + def subject[A](read: Subject[A]): VarEvent[A] = combine(RxEvent.observable(read), RxWriter.observer(read)) + + def combine[A](read: RxEvent[A], write: RxWriter[A]): VarEvent[A] = new VarEventCombine(read, write) +} + trait Var[A] extends RxWriter[A] with Rx[A] { final def updateIfSubscribed(f: A => A): Unit = set(f(nowIfSubscribed())) final def update(f: A => A): Unit = set(f(now())) @@ -178,9 +274,6 @@ trait Var[A] extends RxWriter[A] with Rx[A] { object Var { def apply[A](seed: A): Var[A] = new VarSubject(seed) - def none[A](): Var[Option[A]] = new VarSubject(None) - def some[A](seed: A): Var[Option[A]] = new VarSubject(Some(seed)) - def subjectSync[A](read: Subject[A]): Var[A] = combine(Rx.observableSync(read), RxWriter.observer(read)) def combine[A](read: Rx[A], write: RxWriter[A]): Var[A] = new VarCombine(read, write) @@ -249,6 +342,10 @@ object Var { } } +private class RxEventObservable[A](val observable: Observable[A]) extends RxEvent[A] + +private object RxEventEmpty extends RxEventObservable(Observable.empty) + private final class RxConst[A](value: A) extends Rx[A] { val observable: Observable[A] = Observable.pure(value) def nowOption(): Option[A] = Some(value) @@ -269,6 +366,17 @@ private final class RxObservableSync[A](inner: Observable[A]) extends Rx[A] { private final class RxWriterObserver[A](val observer: Observer[A]) extends RxWriter[A] +private final class VarEventSubject[A](inits: Iterable[A]) extends VarEvent[A] { + private val state = new PublishSubject[A] + val observable: Observable[A] = state.startWith(inits) + val observer: Observer[A] = state +} + +private final class VarEventCombine[A](innerRead: RxEvent[A], innerWrite: RxWriter[A]) extends VarEvent[A] { + val observable = innerRead.observable + val observer = innerWrite.observer +} + private final class VarSubject[A](seed: A) extends Var[A] { private val state = new BehaviorSubject[A](seed) From c77a13bbe13c1a285673c81ba06c840e0c76c914 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Fri, 13 Jan 2023 23:23:28 +0100 Subject: [PATCH 09/44] add tests --- .../scala/colibri/reactive/Reactive.scala | 2 + .../src/test/scala/colibri/ReactiveSpec.scala | 146 ++++++++++++++++++ 2 files changed, 148 insertions(+) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index ce03875f..849a28fc 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -75,6 +75,8 @@ object RxEvent extends RxPlatform { def const[A](value: A): RxEvent[A] = observableUnshared(Observable.pure(value)) + def apply[A](values: A*): RxEvent[A] = observableUnshared(Observable.fromIterable(values)) + def iterable[A](value: Iterable[A]): RxEvent[A] = observableUnshared(Observable.fromIterable(value)) def effect[F[_]: RunEffect, A](value: F[A]): RxEvent[A] = observable(Observable.fromEffect(value)) diff --git a/reactive/src/test/scala/colibri/ReactiveSpec.scala b/reactive/src/test/scala/colibri/ReactiveSpec.scala index 23003aee..ba388005 100644 --- a/reactive/src/test/scala/colibri/ReactiveSpec.scala +++ b/reactive/src/test/scala/colibri/ReactiveSpec.scala @@ -1014,4 +1014,150 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { triggers2 shouldBe List(10, 10, 1, 1, 1) triggerRxCount shouldBe 6 } + + it should "start and stop" in { + var triggers1 = List.empty[Int] + var results1 = List.empty[Int] + + val variable1 = Var(1) + + val cancelable1 = variable1.tap(triggers1 ::= _).unsafeForeach(results1 ::= _) + + triggers1 shouldBe List(1) + results1 shouldBe List(1) + + variable1.set(2) + + triggers1 shouldBe List(2,1) + results1 shouldBe List(2,1) + + cancelable1.unsafeCancel() + variable1.set(3) + + triggers1 shouldBe List(2,1) + results1 shouldBe List(2,1) + + val cancelable1b = variable1.tap(triggers1 ::= _).unsafeForeach(results1 ::= _) + + triggers1 shouldBe List(3,2,1) + results1 shouldBe List(3,2,1) + + variable1.set(4) + + triggers1 shouldBe List(4,3,2,1) + results1 shouldBe List(4,3,2,1) + + cancelable1b.unsafeCancel() + variable1.set(5) + + triggers1 shouldBe List(4,3,2,1) + results1 shouldBe List(4,3,2,1) + } + + "RxEvent" should "combine, start and stop" in { + var triggers1 = List.empty[Int] + var triggers2 = List.empty[Int] + var triggerRxCount = 0 + var results1 = List.empty[Int] + var results2 = List.empty[Int] + + val variable1 = VarEvent(100) + val rx2 = RxEvent(1,2) + val variable1Logged = variable1.tap(triggers1 ::= _) + val rx2Logged = rx2.tap(triggers2 ::= _) + + // use mock locally + val mapped = variable1Logged.combineLatestMap(rx2Logged) { (a,b) => + triggerRxCount += 1 + a + b + } + + triggers1 shouldBe List.empty + triggers2 shouldBe List.empty + triggerRxCount shouldBe 0 + results1 shouldBe List.empty + results2 shouldBe List.empty + + val cancelable1 = mapped.unsafeForeach(results1 ::= _) + + triggers1 shouldBe List(100) + triggers2 shouldBe List(2,1) + triggerRxCount shouldBe 2 + results1 shouldBe List(102,101) + results2 shouldBe List.empty + + variable1.set(200) + + triggers1 shouldBe List(200,100) + triggers2 shouldBe List(2,1) + triggerRxCount shouldBe 3 + results1 shouldBe List(202,102,101) + results2 shouldBe List.empty + + val cancelable2 = mapped.unsafeForeach(results2 ::= _) + + triggers1 shouldBe List(200,100) + triggers2 shouldBe List(2,1) + triggerRxCount shouldBe 3 + results1 shouldBe List(202,102,101) + results2 shouldBe List.empty + + variable1.set(300) + + triggers1 shouldBe List(300,200,100) + triggers2 shouldBe List(2,1) + triggerRxCount shouldBe 4 + results1 shouldBe List(302,202,102,101) + results2 shouldBe List(302) + + cancelable1.unsafeCancel() + + triggers1 shouldBe List(300,200,100) + triggers2 shouldBe List(2,1) + triggerRxCount shouldBe 4 + results1 shouldBe List(302,202,102,101) + results2 shouldBe List(302) + + variable1.set(400) + + triggers1 shouldBe List(400,300,200,100) + triggers2 shouldBe List(2,1) + triggerRxCount shouldBe 5 + results1 shouldBe List(302,202,102,101) + results2 shouldBe List(402,302) + + cancelable2.unsafeCancel() + variable1.set(500) + + triggers1 shouldBe List(400,300,200,100) + triggers2 shouldBe List(2,1) + triggerRxCount shouldBe 5 + results1 shouldBe List(302,202,102,101) + results2 shouldBe List(402,302) + + val cancelable1b = mapped.unsafeForeach(results1 ::= _) + + triggers1 shouldBe List(100,400,300,200,100) + triggers2 shouldBe List(2,1,2,1) + triggerRxCount shouldBe 7 + results1 shouldBe List(102,101,302,202,102,101) + results2 shouldBe List(402,302) + + variable1.set(1000) + + triggers1 shouldBe List(1000,100,400,300,200,100) + triggers2 shouldBe List(2,1,2,1) + triggerRxCount shouldBe 8 + results1 shouldBe List(1002,102,101,302,202,102,101) + results2 shouldBe List(402,302) + + cancelable1b.unsafeCancel() + variable1.set(2000) + + triggers1 shouldBe List(1000,100,400,300,200,100) + triggers2 shouldBe List(2,1,2,1) + triggerRxCount shouldBe 8 + results1 shouldBe List(1002,102,101,302,202,102,101) + results2 shouldBe List(402,302) + } } From 5ddf178b200e6822e32e2955ffc8d8bc81a9ad2d Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Fri, 13 Jan 2023 23:24:42 +0100 Subject: [PATCH 10/44] format --- .../scala/colibri/reactive/Reactive.scala | 45 ++++--- .../src/test/scala/colibri/ReactiveSpec.scala | 120 +++++++++--------- 2 files changed, 84 insertions(+), 81 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 849a28fc..389dbde9 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -32,11 +32,11 @@ object RxSource { } trait RxEvent[+A] extends RxSource[A] { - final def collect[B](f: PartialFunction[A, B]): RxEvent[B] = transformRxEvent(_.collect(f)) - final def map[B](f: A => B): RxEvent[B] = transformRxEvent(_.map(f)) - final def mapEither[B](f: A => Either[Throwable, B]): RxEvent[B] = transformRxEvent(_.mapEither(f)) - final def tap(f: A => Unit): RxEvent[A] = transformRxEvent(_.tap(f)) - final def tapLater(f: A => Unit): RxEvent[A] = transformRxEvent(obs => Observable.concatEffect(obs.headIO, obs.tail.tap(f))) + final def collect[B](f: PartialFunction[A, B]): RxEvent[B] = transformRxEvent(_.collect(f)) + final def map[B](f: A => B): RxEvent[B] = transformRxEvent(_.map(f)) + final def mapEither[B](f: A => Either[Throwable, B]): RxEvent[B] = transformRxEvent(_.mapEither(f)) + final def tap(f: A => Unit): RxEvent[A] = transformRxEvent(_.tap(f)) + final def tapLater(f: A => Unit): RxEvent[A] = transformRxEvent(obs => Observable.concatEffect(obs.headIO, obs.tail.tap(f))) final def mapEffect[F[_]: RunEffect, B](f: A => F[B]): RxEvent[B] = transformRxEvent(_.mapEffect(f)) final def mapFuture[B](f: A => Future[B]): RxEvent[B] = transformRxEvent(_.mapFuture(f)) @@ -51,15 +51,17 @@ trait RxEvent[+A] extends RxSource[A] { final def switchMap[B](f: A => RxEvent[B]): RxEvent[B] = transformRxEvent(_.switchMap(f andThen (_.observable))) final def mergeMap[B](f: A => RxEvent[B]): RxEvent[B] = transformRxEvent(_.mergeMap(f andThen (_.observable))) - final def concatMap[B](f: A => RxEvent[B]): RxEvent[B] = transformRxEvent(_.concatMap(f andThen (_.observable))) + final def concatMap[B](f: A => RxEvent[B]): RxEvent[B] = transformRxEvent(_.concatMap(f andThen (_.observable))) - final def combineLatestMap[B, R](sourceB: RxEvent[B])(f: (A, B) => R): RxEvent[R] = transformRxEvent(_.combineLatestMap(sourceB.observable)(f)) - final def combineLatest[B](sourceB: RxEvent[B]): RxEvent[(A,B)] = transformRxEvent(_.combineLatest(sourceB.observable)) + final def combineLatestMap[B, R](sourceB: RxEvent[B])(f: (A, B) => R): RxEvent[R] = transformRxEvent( + _.combineLatestMap(sourceB.observable)(f), + ) + final def combineLatest[B](sourceB: RxEvent[B]): RxEvent[(A, B)] = transformRxEvent(_.combineLatest(sourceB.observable)) final def withLatestMap[B, R](sourceB: RxEvent[B])(f: (A, B) => R): RxEvent[R] = transformRxEvent(_.withLatestMap(sourceB.observable)(f)) - final def withLatest[B](sourceB: RxEvent[B]): RxEvent[(A,B)] = transformRxEvent(_.withLatest(sourceB.observable)) + final def withLatest[B](sourceB: RxEvent[B]): RxEvent[(A, B)] = transformRxEvent(_.withLatest(sourceB.observable)) - final def transformRxEvent[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) + final def transformRxEvent[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) final def transformRxEventUnshared[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observableUnshared(f(observable)) final def hot: SyncIO[RxEvent[A]] = SyncIO(unsafeHot()) @@ -80,13 +82,13 @@ object RxEvent extends RxPlatform { def iterable[A](value: Iterable[A]): RxEvent[A] = observableUnshared(Observable.fromIterable(value)) def effect[F[_]: RunEffect, A](value: F[A]): RxEvent[A] = observable(Observable.fromEffect(value)) - def future[A](value: => Future[A]): RxEvent[A] = observable(Observable.fromFuture(value)) + def future[A](value: => Future[A]): RxEvent[A] = observable(Observable.fromFuture(value)) - def merge[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.mergeIterable(rxs.map(_.observable))) + def merge[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.mergeIterable(rxs.map(_.observable))) def switch[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.switchIterable(rxs.map(_.observable))) def concat[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.concatIterable(rxs.map(_.observable))) - def observable[A](observable: Observable[A]): RxEvent[A] = new RxEventObservable(observable.publish.refCount) + def observable[A](observable: Observable[A]): RxEvent[A] = new RxEventObservable(observable.publish.refCount) def observableUnshared[A](observable: Observable[A]): RxEvent[A] = new RxEventObservable(observable) @inline implicit final class RxEventOps[A](private val self: RxEvent[A]) extends AnyVal { @@ -233,13 +235,14 @@ object RxWriter { } trait VarEvent[A] extends RxWriter[A] with RxEvent[A] { - final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = VarEvent.combine(g(this), f(this)) - final def transformVarRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.combine(g(this), this) - final def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.combine(this, f(this)) + final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = + VarEvent.combine(g(this), f(this)) + final def transformVarRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.combine(g(this), this) + final def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.combine(this, f(this)) } object VarEvent { - def apply[A](): VarEvent[A] = new VarEventSubject(Nil) + def apply[A](): VarEvent[A] = new VarEventSubject(Nil) def apply[A](seed: A): VarEvent[A] = new VarEventSubject(seed :: Nil) def subject[A](read: Subject[A]): VarEvent[A] = combine(RxEvent.observable(read), RxWriter.observer(read)) @@ -368,15 +371,15 @@ private final class RxObservableSync[A](inner: Observable[A]) extends Rx[A] { private final class RxWriterObserver[A](val observer: Observer[A]) extends RxWriter[A] -private final class VarEventSubject[A](inits: Iterable[A]) extends VarEvent[A] { - private val state = new PublishSubject[A] +private final class VarEventSubject[A](inits: Iterable[A]) extends VarEvent[A] { + private val state = new PublishSubject[A] val observable: Observable[A] = state.startWith(inits) val observer: Observer[A] = state } private final class VarEventCombine[A](innerRead: RxEvent[A], innerWrite: RxWriter[A]) extends VarEvent[A] { - val observable = innerRead.observable - val observer = innerWrite.observer + val observable = innerRead.observable + val observer = innerWrite.observer } private final class VarSubject[A](seed: A) extends Var[A] { diff --git a/reactive/src/test/scala/colibri/ReactiveSpec.scala b/reactive/src/test/scala/colibri/ReactiveSpec.scala index ba388005..1e97f8b1 100644 --- a/reactive/src/test/scala/colibri/ReactiveSpec.scala +++ b/reactive/src/test/scala/colibri/ReactiveSpec.scala @@ -833,7 +833,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { } it should "tapLater" in { - var triggers1 = List.empty[Int] + var triggers1 = List.empty[Int] val variable1 = Var(1) val variable1Logged = variable1.tapLater(triggers1 ::= _) @@ -855,29 +855,29 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { variable1.set(3) - triggers1 shouldBe List(3,2) + triggers1 shouldBe List(3, 2) variable1Logged.nowOption() shouldBe Some(3) - triggers1 shouldBe List(3,2) + triggers1 shouldBe List(3, 2) cancelable.unsafeCancel() variable1.set(4) - triggers1 shouldBe List(3,2) + triggers1 shouldBe List(3, 2) variable1Logged.nowOption() shouldBe None - triggers1 shouldBe List(3,2) + triggers1 shouldBe List(3, 2) variable1Logged.now() shouldBe 4 - triggers1 shouldBe List(3,2) + triggers1 shouldBe List(3, 2) variable1.set(5) - triggers1 shouldBe List(3,2) + triggers1 shouldBe List(3, 2) variable1Logged.nowOption() shouldBe None - triggers1 shouldBe List(3,2) + triggers1 shouldBe List(3, 2) variable1Logged.now() shouldBe 5 - triggers1 shouldBe List(3,2) + triggers1 shouldBe List(3, 2) } it should "subscribe and now on rx with lazy subscriptions" in { @@ -1016,8 +1016,8 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { } it should "start and stop" in { - var triggers1 = List.empty[Int] - var results1 = List.empty[Int] + var triggers1 = List.empty[Int] + var results1 = List.empty[Int] val variable1 = Var(1) @@ -1028,46 +1028,46 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { variable1.set(2) - triggers1 shouldBe List(2,1) - results1 shouldBe List(2,1) + triggers1 shouldBe List(2, 1) + results1 shouldBe List(2, 1) cancelable1.unsafeCancel() variable1.set(3) - triggers1 shouldBe List(2,1) - results1 shouldBe List(2,1) + triggers1 shouldBe List(2, 1) + results1 shouldBe List(2, 1) val cancelable1b = variable1.tap(triggers1 ::= _).unsafeForeach(results1 ::= _) - triggers1 shouldBe List(3,2,1) - results1 shouldBe List(3,2,1) + triggers1 shouldBe List(3, 2, 1) + results1 shouldBe List(3, 2, 1) variable1.set(4) - triggers1 shouldBe List(4,3,2,1) - results1 shouldBe List(4,3,2,1) + triggers1 shouldBe List(4, 3, 2, 1) + results1 shouldBe List(4, 3, 2, 1) cancelable1b.unsafeCancel() variable1.set(5) - triggers1 shouldBe List(4,3,2,1) - results1 shouldBe List(4,3,2,1) + triggers1 shouldBe List(4, 3, 2, 1) + results1 shouldBe List(4, 3, 2, 1) } "RxEvent" should "combine, start and stop" in { var triggers1 = List.empty[Int] var triggers2 = List.empty[Int] var triggerRxCount = 0 - var results1 = List.empty[Int] - var results2 = List.empty[Int] + var results1 = List.empty[Int] + var results2 = List.empty[Int] val variable1 = VarEvent(100) - val rx2 = RxEvent(1,2) + val rx2 = RxEvent(1, 2) val variable1Logged = variable1.tap(triggers1 ::= _) - val rx2Logged = rx2.tap(triggers2 ::= _) + val rx2Logged = rx2.tap(triggers2 ::= _) // use mock locally - val mapped = variable1Logged.combineLatestMap(rx2Logged) { (a,b) => + val mapped = variable1Logged.combineLatestMap(rx2Logged) { (a, b) => triggerRxCount += 1 a + b } @@ -1081,83 +1081,83 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { val cancelable1 = mapped.unsafeForeach(results1 ::= _) triggers1 shouldBe List(100) - triggers2 shouldBe List(2,1) + triggers2 shouldBe List(2, 1) triggerRxCount shouldBe 2 - results1 shouldBe List(102,101) + results1 shouldBe List(102, 101) results2 shouldBe List.empty variable1.set(200) - triggers1 shouldBe List(200,100) - triggers2 shouldBe List(2,1) + triggers1 shouldBe List(200, 100) + triggers2 shouldBe List(2, 1) triggerRxCount shouldBe 3 - results1 shouldBe List(202,102,101) + results1 shouldBe List(202, 102, 101) results2 shouldBe List.empty val cancelable2 = mapped.unsafeForeach(results2 ::= _) - triggers1 shouldBe List(200,100) - triggers2 shouldBe List(2,1) + triggers1 shouldBe List(200, 100) + triggers2 shouldBe List(2, 1) triggerRxCount shouldBe 3 - results1 shouldBe List(202,102,101) + results1 shouldBe List(202, 102, 101) results2 shouldBe List.empty variable1.set(300) - triggers1 shouldBe List(300,200,100) - triggers2 shouldBe List(2,1) + triggers1 shouldBe List(300, 200, 100) + triggers2 shouldBe List(2, 1) triggerRxCount shouldBe 4 - results1 shouldBe List(302,202,102,101) + results1 shouldBe List(302, 202, 102, 101) results2 shouldBe List(302) cancelable1.unsafeCancel() - triggers1 shouldBe List(300,200,100) - triggers2 shouldBe List(2,1) + triggers1 shouldBe List(300, 200, 100) + triggers2 shouldBe List(2, 1) triggerRxCount shouldBe 4 - results1 shouldBe List(302,202,102,101) + results1 shouldBe List(302, 202, 102, 101) results2 shouldBe List(302) variable1.set(400) - triggers1 shouldBe List(400,300,200,100) - triggers2 shouldBe List(2,1) + triggers1 shouldBe List(400, 300, 200, 100) + triggers2 shouldBe List(2, 1) triggerRxCount shouldBe 5 - results1 shouldBe List(302,202,102,101) - results2 shouldBe List(402,302) + results1 shouldBe List(302, 202, 102, 101) + results2 shouldBe List(402, 302) cancelable2.unsafeCancel() variable1.set(500) - triggers1 shouldBe List(400,300,200,100) - triggers2 shouldBe List(2,1) + triggers1 shouldBe List(400, 300, 200, 100) + triggers2 shouldBe List(2, 1) triggerRxCount shouldBe 5 - results1 shouldBe List(302,202,102,101) - results2 shouldBe List(402,302) + results1 shouldBe List(302, 202, 102, 101) + results2 shouldBe List(402, 302) val cancelable1b = mapped.unsafeForeach(results1 ::= _) - triggers1 shouldBe List(100,400,300,200,100) - triggers2 shouldBe List(2,1,2,1) + triggers1 shouldBe List(100, 400, 300, 200, 100) + triggers2 shouldBe List(2, 1, 2, 1) triggerRxCount shouldBe 7 - results1 shouldBe List(102,101,302,202,102,101) - results2 shouldBe List(402,302) + results1 shouldBe List(102, 101, 302, 202, 102, 101) + results2 shouldBe List(402, 302) variable1.set(1000) - triggers1 shouldBe List(1000,100,400,300,200,100) - triggers2 shouldBe List(2,1,2,1) + triggers1 shouldBe List(1000, 100, 400, 300, 200, 100) + triggers2 shouldBe List(2, 1, 2, 1) triggerRxCount shouldBe 8 - results1 shouldBe List(1002,102,101,302,202,102,101) - results2 shouldBe List(402,302) + results1 shouldBe List(1002, 102, 101, 302, 202, 102, 101) + results2 shouldBe List(402, 302) cancelable1b.unsafeCancel() variable1.set(2000) - triggers1 shouldBe List(1000,100,400,300,200,100) - triggers2 shouldBe List(2,1,2,1) + triggers1 shouldBe List(1000, 100, 400, 300, 200, 100) + triggers2 shouldBe List(2, 1, 2, 1) triggerRxCount shouldBe 8 - results1 shouldBe List(1002,102,101,302,202,102,101) - results2 shouldBe List(402,302) + results1 shouldBe List(1002, 102, 101, 302, 202, 102, 101) + results2 shouldBe List(402, 302) } } From 31f2a1c33d3af0e9ebb680b1591e11949293d09e Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Fri, 13 Jan 2023 23:53:44 +0100 Subject: [PATCH 11/44] update readme --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 64e2f60d..d1dd913a 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ println(variable2.now()) // "Foo" println(rx.now()) // "3 - Foo" ``` -[Outwatch](https://github.com/outwatch/outwatch) works perfectly with Rx as just like Observable. +[Outwatch](https://github.com/outwatch/outwatch) works perfectly with Rx - just like Observable. ```scala @@ -222,6 +222,21 @@ val component: VModifier = { } ``` +There also exist `RxEvent` and `VarEvent`, which are event observables with shared execution. That is they behave like `Rx` and `Var` such that transformations are only applied once and not per subscription. But `RxEvent` and `VarEvent` are not distinct and have no current value. They should be used for event streams. + +``` +import colibri.reactive._ + +val variable = VarEvent[Int]() + +val stream1 = RxEvent.empty +val stream2 = RxEvent.const(1) + +val mapped = RxEvent.merge(variable.tap(println(_)).map(_ + 1), stream1, stream2) + +val cancelable = mapped.unsafeForeach(println(_)) +``` + ### Memory management The same principles as for Observables hold. Any cancelable that is returned from the API needs to be handled by the the caller. Best practice: use subscribe/foreach as seldomly as possible - only in selected spots or within a library. From 7af27ca64111dfa930cc59257a7caff4f51d2e8f Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Sat, 14 Jan 2023 02:02:09 +0100 Subject: [PATCH 12/44] wip --- .../scala/colibri/reactive/Reactive.scala | 143 ++++++++++++++++-- 1 file changed, 134 insertions(+), 9 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 389dbde9..37821f29 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -75,14 +75,13 @@ trait RxEvent[+A] extends RxSource[A] { object RxEvent extends RxPlatform { def empty[A]: RxEvent[A] = RxEventEmpty - def const[A](value: A): RxEvent[A] = observableUnshared(Observable.pure(value)) - def apply[A](values: A*): RxEvent[A] = observableUnshared(Observable.fromIterable(values)) + def apply[A](values: A*): RxEvent[A] = iterable(values) - def iterable[A](value: Iterable[A]): RxEvent[A] = observableUnshared(Observable.fromIterable(value)) + def iterable[A](values: Iterable[A]): RxEvent[A] = observableUnshared(Observable.fromIterable(values)) - def effect[F[_]: RunEffect, A](value: F[A]): RxEvent[A] = observable(Observable.fromEffect(value)) - def future[A](value: => Future[A]): RxEvent[A] = observable(Observable.fromFuture(value)) + def future[A](future: => Future[A]): RxEvent[A] = observable(Observable.fromFuture(future)) + def effect[F[_]: RunEffect, A](effect: F[A]): RxEvent[A] = observable(Observable.fromEffect(effect)) def merge[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.mergeIterable(rxs.map(_.observable))) def switch[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.switchIterable(rxs.map(_.observable))) @@ -111,6 +110,73 @@ object RxEvent extends RxPlatform { } } +trait RxLater[+A] extends RxSource[A] { + def nowOption(): Option[Option[A]] + + def apply()(implicit owner: LiveOwner): Option[A] + def now()(implicit owner: NowOwner): Option[A] + + final def nowIfSubscribed(): Option[A] = nowOption().getOrElse(throw RxMissingNowException) + + final def collect[B](f: PartialFunction[A, B]): RxLater[B] = transformRxLater(_.collect(f)) + final def map[B](f: A => B): RxLater[B] = transformRxLater(_.map(f)) + final def mapEither[B](f: A => Either[Throwable, B]): RxLater[B] = transformRxLater(_.mapEither(f)) + final def tap(f: A => Unit): RxLater[A] = transformRxLater(_.tap(f)) + + final def mapSyncEffect[F[_]: RunSyncEffect, B](f: A => F[B]): RxLater[B] = transformRxLater(_.mapEffect(f)) + final def mapEffect[F[_]: RunEffect, B](f: A => F[B]): RxLater[B] = transformRxLater(_.mapEffect(f)) + final def mapFuture[B](f: A => Future[B]): RxLater[B] = transformRxLater(_.mapFuture(f)) + + final def as[B](value: B): RxLater[B] = transformRxLater(_.as(value)) + final def asEval[B](value: => B): RxLater[B] = transformRxLater(_.asEval(value)) + + final def asSyncEffect[F[_]: RunSyncEffect, B](value: F[B]): RxLater[B] = transformRxLater(_.asEffect(value)) + final def asEffect[F[_]: RunEffect, B](value: F[B]): RxLater[B] = transformRxLater(_.asEffect(value)) + final def asFuture[B](value: => Future[B]): RxLater[B] = transformRxLater(_.asFuture(value)) + + final def via(writer: RxWriter[A]): RxLater[A] = transformRxLater(_.via(writer.observer)) + + final def switchMap[B](f: A => RxSource[B]): RxLater[B] = transformRxLater(_.switchMap(f andThen (_.observable))) + final def mergeMap[B](f: A => RxSource[B]): RxLater[B] = transformRxLater(_.mergeMap(f andThen (_.observable))) + + final def transformRxLater[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) + + final def hot: SyncIO[RxLater[A]] = SyncIO(unsafeHot()) + + final def unsafeHot(): RxLater[A] = { + val _ = unsafeSubscribe() + this + } +} + +object RxLater { + def empty[A]: RxLater[A] = RxLaterEmpty + + def const[A](value: A): RxLater[A] = new RxLaterConst(value) + + def future[A](future: => Future[A]): RxLater[A] = observable(Observable.fromFuture(future)) + def effect[F[_]: RunEffect, A](effect: F[A]): RxLater[A] = observable(Observable.fromEffect(effect)) + + def observable[A](observable: Observable[A]): RxLater[A] = new RxLaterObservable(observable) + + @inline implicit final class RxLaterOps[A](private val self: RxLater[A]) extends AnyVal { + def scan[B](seed: => B)(f: (B, A) => B): RxLater[B] = self.transformRxLater(_.scan0(seed)(f)) + + def filter(f: A => Boolean): RxLater[A] = self.transformRxLater(_.filter(f)) + } + + @inline implicit class RxLaterBooleanOps(private val source: RxLater[Boolean]) extends AnyVal { + @inline def toggle[A](ifTrue: => A, ifFalse: => A): RxLater[A] = source.map { + case true => ifTrue + case false => ifFalse + } + + @inline def toggle[A: Monoid](ifTrue: => A): RxLater[A] = toggle(ifTrue, Monoid[A].empty) + + @inline def negated: RxLater[Boolean] = source.map(x => !x) + } +} + trait Rx[+A] extends RxSource[A] { def nowOption(): Option[A] @@ -235,21 +301,38 @@ object RxWriter { } trait VarEvent[A] extends RxWriter[A] with RxEvent[A] { - final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = + final def transformVarEvent[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = VarEvent.combine(g(this), f(this)) - final def transformVarRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.combine(g(this), this) - final def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.combine(this, f(this)) + final def transformVarEventRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.combine(g(this), this) + final def transformVarEventRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.combine(this, f(this)) } object VarEvent { def apply[A](): VarEvent[A] = new VarEventSubject(Nil) - def apply[A](seed: A): VarEvent[A] = new VarEventSubject(seed :: Nil) + + def apply[A](value: A): VarEvent[A] = new VarEventSubject(value :: Nil) def subject[A](read: Subject[A]): VarEvent[A] = combine(RxEvent.observable(read), RxWriter.observer(read)) def combine[A](read: RxEvent[A], write: RxWriter[A]): VarEvent[A] = new VarEventCombine(read, write) } +trait VarLater[A] extends RxWriter[A] with RxLater[A] { + final def transformVarLater[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = VarLater.combine(g(this), f(this)) + final def transformVarLaterRx(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.combine(g(this), this) + final def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.combine(this, f(this)) +} + +object VarLater { + def apply[A](): VarLater[A] = new VarLaterSubject(None) + + def apply[A](value: A): VarLater[A] = new VarLaterSubject(Some(value)) + + def subject[A](read: Subject[A]): VarLater[A] = combine(RxLater.observable(read), RxWriter.observer(read)) + + def combine[A](read: RxLater[A], write: RxWriter[A]): VarLater[A] = new VarLaterCombine(read, write) +} + trait Var[A] extends RxWriter[A] with Rx[A] { final def updateIfSubscribed(f: A => A): Unit = set(f(nowIfSubscribed())) final def update(f: A => A): Unit = set(f(now())) @@ -351,6 +434,30 @@ private class RxEventObservable[A](val observable: Observable[A]) extends RxEven private object RxEventEmpty extends RxEventObservable(Observable.empty) +private final object RxLaterEmpty extends RxLater[Nothing] { + val observable = Observable.empty + def nowOption() = Some(None) + def now()(implicit owner: NowOwner) = None + def apply()(implicit owner: LiveOwner) = None +} + +private final class RxLaterConst[A](value: A) extends RxLater[A] { + val observable = Observable.pure(value) + def nowOption() = Some(Some(value)) + def now()(implicit owner: NowOwner) = Some(value) + def apply()(implicit owner: LiveOwner) = Some(value) +} + +private final class RxLaterObservable[A](inner: Observable[A]) extends RxLater[A] { + private val state = Rx.observable(inner.map[Option[A]](Some.apply))(None) + + val observable: Observable[A] = state.observable.flattenOption.distinctOnEquals + + def nowOption() = state.nowOption() + def now()(implicit owner: NowOwner) = state.now() + def apply()(implicit owner: LiveOwner) = state() +} + private final class RxConst[A](value: A) extends Rx[A] { val observable: Observable[A] = Observable.pure(value) def nowOption(): Option[A] = Some(value) @@ -382,6 +489,24 @@ private final class VarEventCombine[A](innerRead: RxEvent[A], innerWrite: RxWrit val observer = innerWrite.observer } +private final class VarLaterSubject[A](seed: Option[A]) extends VarLater[A] { + private val state = Var(seed) + + val observable: Observable[A] = state.observable.flattenOption.distinctOnEquals + val observer: Observer[A] = state.observer.contramap(Some.apply) + + def nowOption() = state.nowOption() + def now()(implicit owner: NowOwner) = state.now() + def apply()(implicit owner: LiveOwner) = state() +} +private final class VarLaterCombine[A](innerRead: RxLater[A], innerWrite: RxWriter[A]) extends VarLater[A] { + def nowOption() = innerRead.nowOption() + def now()(implicit owner: NowOwner) = innerRead.now() + def apply()(implicit owner: LiveOwner) = innerRead() + val observable = innerRead.observable + val observer = innerWrite.observer +} + private final class VarSubject[A](seed: A) extends Var[A] { private val state = new BehaviorSubject[A](seed) From 1a8b5bc912759edce12ba051ac69ad48a97cc9ca Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Sat, 14 Jan 2023 02:02:36 +0100 Subject: [PATCH 13/44] delete duplicate distinct --- reactive/src/main/scala/colibri/reactive/Reactive.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 37821f29..a586511a 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -469,7 +469,7 @@ private final class RxObservableSync[A](inner: Observable[A]) extends Rx[A] { private val state = new ReplayLatestSubject[A]() val observable: Observable[A] = - inner.dropUntilSyncLatest.distinctOnEquals.tapCancel(state.unsafeResetState).multicast(state).refCount.distinctOnEquals + inner.dropUntilSyncLatest.distinctOnEquals.tapCancel(state.unsafeResetState).multicast(state).refCount def nowOption() = state.now() def now()(implicit owner: NowOwner) = owner.unsafeNow(this) From 5933f80fbb8a457860d716ba7056092957e1cd12 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 16 Jan 2023 11:36:47 +0100 Subject: [PATCH 14/44] wip --- .../scala/colibri/reactive/Reactive.scala | 290 ++++++++---------- 1 file changed, 132 insertions(+), 158 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index a586511a..1985efee 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -22,7 +22,6 @@ trait RxSource[+A] { final def unsafeSubscribe(writer: RxWriter[A]): Cancelable = observable.unsafeSubscribe(writer.observer) final def unsafeForeach(f: A => Unit): Cancelable = observable.unsafeForeach(f) - final def unsafeForeachLater(f: A => Unit): Cancelable = observable.tail.unsafeForeach(f) } object RxSource { @@ -32,49 +31,44 @@ object RxSource { } trait RxEvent[+A] extends RxSource[A] { - final def collect[B](f: PartialFunction[A, B]): RxEvent[B] = transformRxEvent(_.collect(f)) - final def map[B](f: A => B): RxEvent[B] = transformRxEvent(_.map(f)) - final def mapEither[B](f: A => Either[Throwable, B]): RxEvent[B] = transformRxEvent(_.mapEither(f)) - final def tap(f: A => Unit): RxEvent[A] = transformRxEvent(_.tap(f)) - final def tapLater(f: A => Unit): RxEvent[A] = transformRxEvent(obs => Observable.concatEffect(obs.headIO, obs.tail.tap(f))) + def collect[B](f: PartialFunction[A, B]): RxEvent[B] = transformRxEvent(_.collect(f)) + def map[B](f: A => B): RxEvent[B] = transformRxEvent(_.map(f)) + def mapEither[B](f: A => Either[Throwable, B]): RxEvent[B] = transformRxEvent(_.mapEither(f)) + def tap(f: A => Unit): RxEvent[A] = transformRxEvent(_.tap(f)) - final def mapEffect[F[_]: RunEffect, B](f: A => F[B]): RxEvent[B] = transformRxEvent(_.mapEffect(f)) - final def mapFuture[B](f: A => Future[B]): RxEvent[B] = transformRxEvent(_.mapFuture(f)) + def mapEffect[F[_]: RunEffect, B](f: A => F[B]): RxEvent[B] = transformRxEvent(_.mapEffect(f)) + def mapFuture[B](f: A => Future[B]): RxEvent[B] = transformRxEvent(_.mapFuture(f)) - final def as[B](value: B): RxEvent[B] = transformRxEvent(_.as(value)) - final def asEval[B](value: => B): RxEvent[B] = transformRxEvent(_.asEval(value)) + def as[B](value: B): RxEvent[B] = transformRxEvent(_.as(value)) + def asEval[B](value: => B): RxEvent[B] = transformRxEvent(_.asEval(value)) - final def asEffect[F[_]: RunEffect, B](value: F[B]): RxEvent[B] = transformRxEvent(_.asEffect(value)) - final def asFuture[B](value: => Future[B]): RxEvent[B] = transformRxEvent(_.asFuture(value)) + def asEffect[F[_]: RunEffect, B](value: F[B]): RxEvent[B] = transformRxEvent(_.asEffect(value)) + def asFuture[B](value: => Future[B]): RxEvent[B] = transformRxEvent(_.asFuture(value)) - final def via(writer: RxWriter[A]): RxEvent[A] = transformRxEvent(_.via(writer.observer)) + def via(writer: RxWriter[A]): RxEvent[A] = transformRxEvent(_.via(writer.observer)) - final def switchMap[B](f: A => RxEvent[B]): RxEvent[B] = transformRxEvent(_.switchMap(f andThen (_.observable))) - final def mergeMap[B](f: A => RxEvent[B]): RxEvent[B] = transformRxEvent(_.mergeMap(f andThen (_.observable))) - final def concatMap[B](f: A => RxEvent[B]): RxEvent[B] = transformRxEvent(_.concatMap(f andThen (_.observable))) + def switchMap[B](f: A => RxSource[B]): RxEvent[B] = transformRxEvent(_.switchMap(f andThen (_.observable))) + def mergeMap[B](f: A => RxSource[B]): RxEvent[B] = transformRxEvent(_.mergeMap(f andThen (_.observable))) + def concatMap[B](f: A => RxSource[B]): RxEvent[B] = transformRxEvent(_.concatMap(f andThen (_.observable))) - final def combineLatestMap[B, R](sourceB: RxEvent[B])(f: (A, B) => R): RxEvent[R] = transformRxEvent( + def combineLatestMap[B, R](sourceB: RxEvent[B])(f: (A, B) => R): RxEvent[R] = transformRxEvent( _.combineLatestMap(sourceB.observable)(f), ) - final def combineLatest[B](sourceB: RxEvent[B]): RxEvent[(A, B)] = transformRxEvent(_.combineLatest(sourceB.observable)) + def combineLatest[B](sourceB: RxEvent[B]): RxEvent[(A, B)] = transformRxEvent(_.combineLatest(sourceB.observable)) - final def withLatestMap[B, R](sourceB: RxEvent[B])(f: (A, B) => R): RxEvent[R] = transformRxEvent(_.withLatestMap(sourceB.observable)(f)) - final def withLatest[B](sourceB: RxEvent[B]): RxEvent[(A, B)] = transformRxEvent(_.withLatest(sourceB.observable)) + def withLatestMap[B, R](sourceB: RxEvent[B])(f: (A, B) => R): RxEvent[R] = transformRxEvent(_.withLatestMap(sourceB.observable)(f)) + def withLatest[B](sourceB: RxEvent[B]): RxEvent[(A, B)] = transformRxEvent(_.withLatest(sourceB.observable)) - final def transformRxEvent[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) - final def transformRxEventUnshared[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observableUnshared(f(observable)) + def hot: SyncIO[RxEvent[A]] = SyncIO(unsafeHot()) + def unsafeHot(): RxEvent[A] = { val _ = unsafeSubscribe(); this } - final def hot: SyncIO[RxEvent[A]] = SyncIO(unsafeHot()) - - final def unsafeHot(): RxEvent[A] = { - val _ = unsafeSubscribe() - this - } + def transformRxEvent[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) + def transformRxEventUnshared[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observableUnshared(f(observable)) } object RxEvent extends RxPlatform { - def empty[A]: RxEvent[A] = RxEventEmpty - + private val _empty: RxEvent[Nothing] = observableUnshared(Observable.empty) + def empty[A]: RxEvent[A] = _empty def apply[A](values: A*): RxEvent[A] = iterable(values) @@ -98,7 +92,7 @@ object RxEvent extends RxPlatform { def prepend(value: A): RxEvent[A] = self.transformRxEvent(_.prepend(value)) } - @inline implicit class RxEventBooleanOps(private val source: RxEvent[Boolean]) extends AnyVal { + @inline implicit final class RxEventBooleanOps(private val source: RxEvent[Boolean]) extends AnyVal { @inline def toggle[A](ifTrue: => A, ifFalse: => A): RxEvent[A] = source.map { case true => ifTrue case false => ifFalse @@ -111,48 +105,43 @@ object RxEvent extends RxPlatform { } trait RxLater[+A] extends RxSource[A] { - def nowOption(): Option[Option[A]] + def nowOptionOpt(): Option[Option[A]] - def apply()(implicit owner: LiveOwner): Option[A] - def now()(implicit owner: NowOwner): Option[A] + def applyOpt()(implicit owner: LiveOwner): Option[A] + def nowOpt()(implicit owner: NowOwner): Option[A] - final def nowIfSubscribed(): Option[A] = nowOption().getOrElse(throw RxMissingNowException) + final def nowIfSubscribedOpt(): Option[A] = nowOptionOpt().getOrElse(throw RxMissingNowException) - final def collect[B](f: PartialFunction[A, B]): RxLater[B] = transformRxLater(_.collect(f)) - final def map[B](f: A => B): RxLater[B] = transformRxLater(_.map(f)) - final def mapEither[B](f: A => Either[Throwable, B]): RxLater[B] = transformRxLater(_.mapEither(f)) - final def tap(f: A => Unit): RxLater[A] = transformRxLater(_.tap(f)) + def collect[B](f: PartialFunction[A, B]): RxLater[B] = transformRxLater(_.collect(f)) + def map[B](f: A => B): RxLater[B] = transformRxLater(_.map(f)) + def mapEither[B](f: A => Either[Throwable, B]): RxLater[B] = transformRxLater(_.mapEither(f)) + def tap(f: A => Unit): RxLater[A] = transformRxLater(_.tap(f)) + def drop(n: Int): RxLater[A] = transformRxLater(_.drop(n)) - final def mapSyncEffect[F[_]: RunSyncEffect, B](f: A => F[B]): RxLater[B] = transformRxLater(_.mapEffect(f)) - final def mapEffect[F[_]: RunEffect, B](f: A => F[B]): RxLater[B] = transformRxLater(_.mapEffect(f)) - final def mapFuture[B](f: A => Future[B]): RxLater[B] = transformRxLater(_.mapFuture(f)) + def mapEffect[F[_]: RunEffect, B](f: A => F[B]): RxLater[B] = transformRxLater(_.mapEffect(f)) + def mapFuture[B](f: A => Future[B]): RxLater[B] = transformRxLater(_.mapFuture(f)) - final def as[B](value: B): RxLater[B] = transformRxLater(_.as(value)) - final def asEval[B](value: => B): RxLater[B] = transformRxLater(_.asEval(value)) + def as[B](value: B): RxLater[B] = transformRxLater(_.as(value)) + def asEval[B](value: => B): RxLater[B] = transformRxLater(_.asEval(value)) - final def asSyncEffect[F[_]: RunSyncEffect, B](value: F[B]): RxLater[B] = transformRxLater(_.asEffect(value)) - final def asEffect[F[_]: RunEffect, B](value: F[B]): RxLater[B] = transformRxLater(_.asEffect(value)) - final def asFuture[B](value: => Future[B]): RxLater[B] = transformRxLater(_.asFuture(value)) + def asEffect[F[_]: RunEffect, B](value: F[B]): RxLater[B] = transformRxLater(_.asEffect(value)) + def asFuture[B](value: => Future[B]): RxLater[B] = transformRxLater(_.asFuture(value)) - final def via(writer: RxWriter[A]): RxLater[A] = transformRxLater(_.via(writer.observer)) + def via(writer: RxWriter[A]): RxLater[A] = transformRxLater(_.via(writer.observer)) - final def switchMap[B](f: A => RxSource[B]): RxLater[B] = transformRxLater(_.switchMap(f andThen (_.observable))) - final def mergeMap[B](f: A => RxSource[B]): RxLater[B] = transformRxLater(_.mergeMap(f andThen (_.observable))) + def switchMap[B](f: A => RxSource[B]): RxLater[B] = transformRxLater(_.switchMap(f andThen (_.observable))) + def mergeMap[B](f: A => RxSource[B]): RxLater[B] = transformRxLater(_.mergeMap(f andThen (_.observable))) - final def transformRxLater[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) + def hot: SyncIO[RxLater[A]] = SyncIO(unsafeHot()) + def unsafeHot(): RxLater[A] = { val _ = unsafeSubscribe(); this } - final def hot: SyncIO[RxLater[A]] = SyncIO(unsafeHot()) - - final def unsafeHot(): RxLater[A] = { - val _ = unsafeSubscribe() - this - } + def transformRxLater[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) } object RxLater { def empty[A]: RxLater[A] = RxLaterEmpty - def const[A](value: A): RxLater[A] = new RxLaterConst(value) + def const[A](value: A): RxLater[A] = new RxConst(value) def future[A](future: => Future[A]): RxLater[A] = observable(Observable.fromFuture(future)) def effect[F[_]: RunEffect, A](effect: F[A]): RxLater[A] = observable(Observable.fromEffect(effect)) @@ -163,9 +152,12 @@ object RxLater { def scan[B](seed: => B)(f: (B, A) => B): RxLater[B] = self.transformRxLater(_.scan0(seed)(f)) def filter(f: A => Boolean): RxLater[A] = self.transformRxLater(_.filter(f)) + + def toRx: Rx[Option[A]] = Rx.observableSeed(self.observable.map[Option[A]](Some.apply))(None) + def toRx(seed: => A): Rx[A] = Rx.observableSeed(self.observable)(seed) } - @inline implicit class RxLaterBooleanOps(private val source: RxLater[Boolean]) extends AnyVal { + @inline implicit final class RxLaterBooleanOps(private val source: RxLater[Boolean]) extends AnyVal { @inline def toggle[A](ifTrue: => A, ifFalse: => A): RxLater[A] = source.map { case true => ifTrue case false => ifFalse @@ -177,45 +169,34 @@ object RxLater { } } -trait Rx[+A] extends RxSource[A] { +trait Rx[+A] extends RxLater[A] { def nowOption(): Option[A] def apply()(implicit owner: LiveOwner): A def now()(implicit owner: NowOwner): A - final def nowIfSubscribed(): A = nowOption().getOrElse(throw RxMissingNowException) - - final def collect[B](f: PartialFunction[A, B])(seed: => B): Rx[B] = transformRx(_.collect(f))(seed) - final def map[B](f: A => B): Rx[B] = transformRxSync(_.map(f)) - final def mapEither[B](f: A => Either[Throwable, B]): Rx[B] = transformRxSync(_.mapEither(f)) - final def tap(f: A => Unit): Rx[A] = transformRxSync(_.tap(f)) - final def tapLater(f: A => Unit): Rx[A] = transformRx(_.tail.tap(f))(nowIfSubscribed()) - - final def mapSyncEffect[F[_]: RunSyncEffect, B](f: A => F[B]): Rx[B] = transformRxSync(_.mapEffect(f)) - final def mapEffect[F[_]: RunEffect, B](f: A => F[B])(seed: => B): Rx[B] = transformRx(_.mapEffect(f))(seed) - final def mapFuture[B](f: A => Future[B])(seed: => B): Rx[B] = transformRx(_.mapFuture(f))(seed) - - final def as[B](value: B): Rx[B] = transformRxSync(_.as(value)) - final def asEval[B](value: => B): Rx[B] = transformRxSync(_.asEval(value)) + override def nowOptionOpt(): Option[Option[A]] = Some(nowOption()) - final def asSyncEffect[F[_]: RunSyncEffect, B](value: F[B]): Rx[B] = transformRxSync(_.asEffect(value)) - final def asEffect[F[_]: RunEffect, B](value: F[B])(seed: => B): Rx[B] = transformRx(_.asEffect(value))(seed) - final def asFuture[B](value: => Future[B])(seed: => B): Rx[B] = transformRx(_.asFuture(value))(seed) + override def applyOpt()(implicit owner: LiveOwner): Option[A] = Some(apply()) + override def nowOpt()(implicit owner: NowOwner): Option[A] = Some(now()) - final def via(writer: RxWriter[A]): Rx[A] = transformRxSync(_.via(writer.observer)) + final def nowIfSubscribed(): A = nowOption().getOrElse(throw RxMissingNowException) - final def switchMap[B](f: A => Rx[B]): Rx[B] = transformRxSync(_.switchMap(f andThen (_.observable))) - final def mergeMap[B](f: A => Rx[B]): Rx[B] = transformRxSync(_.mergeMap(f andThen (_.observable))) + override def map[B](f: A => B): Rx[B] = transformRxSync(_.map(f)) + override def mapEither[B](f: A => Either[Throwable, B]): Rx[B] = transformRxSync(_.mapEither(f)) + override def tap(f: A => Unit): Rx[A] = transformRxSync(_.tap(f)) + override def as[B](value: B): Rx[B] = transformRxSync(_.as(value)) + override def asEval[B](value: => B): Rx[B] = transformRxSync(_.asEval(value)) + override def via(writer: RxWriter[A]): Rx[A] = transformRxSync(_.via(writer.observer)) + override def hot: SyncIO[Rx[A]] = SyncIO(unsafeHot()) + override def unsafeHot(): Rx[A] = { val _ = unsafeSubscribe(); this } - final def transformRx[B](f: Observable[A] => Observable[B])(seed: => B): Rx[B] = Rx.observable(f(observable))(seed) - final def transformRxSync[B](f: Observable[A] => Observable[B]): Rx[B] = Rx.observableSync(f(observable)) + def mapSyncEffect[F[_]: RunSyncEffect, B](f: A => F[B]): Rx[B] = transformRxSync(_.mapEffect(f)) - final def hot: SyncIO[Rx[A]] = SyncIO(unsafeHot()) + def asSyncEffect[F[_]: RunSyncEffect, B](value: F[B]): Rx[B] = transformRxSync(_.asEffect(value)) - final def unsafeHot(): Rx[A] = { - val _ = unsafeSubscribe() - this - } + def transformRxSeed[B](f: Observable[A] => Observable[B])(seed: => B): Rx[B] = Rx.observableSeed(f(observable))(seed) + def transformRxSync[B](f: Observable[A] => Observable[B]): Rx[B] = Rx.observableSync(f(observable)) } object Rx extends RxPlatform { @@ -244,19 +225,17 @@ object Rx extends RxPlatform { def const[A](value: A): Rx[A] = new RxConst(value) - def observable[A](observable: Observable[A])(seed: => A): Rx[A] = observableSync(observable.prependEval(seed)) + def observableSeed[A](observable: Observable[A])(seed: => A): Rx[A] = observableSync(observable.prependEval(seed)) - def observableSync[A](observable: Observable[A]): Rx[A] = new RxObservableSync(observable) + def observableSync[A](observable: Observable[A]): Rx[A] = new RxSyncObservable(observable) @inline implicit final class RxOps[A](private val self: Rx[A]) extends AnyVal { def scanReduce(f: (A, A) => A): Rx[A] = scan(self.nowIfSubscribed())(f) def scan[B](seed: => B)(f: (B, A) => B): Rx[B] = self.transformRxSync(_.scan0(seed)(f)) - - def filter(f: A => Boolean)(seed: => A): Rx[A] = self.transformRx(_.filter(f))(seed) } - @inline implicit class RxBooleanOps(private val source: Rx[Boolean]) extends AnyVal { + @inline implicit final class RxBooleanOps(private val source: Rx[Boolean]) extends AnyVal { @inline def toggle[A](ifTrue: => A, ifFalse: => A): Rx[A] = source.map { case true => ifTrue case false => ifFalse @@ -271,15 +250,15 @@ object Rx extends RxPlatform { trait RxWriter[-A] { def observer: Observer[A] - final def set(value: A): Unit = observer.unsafeOnNext(value) + def set(value: A): Unit = observer.unsafeOnNext(value) - final def as(value: A): RxWriter[Any] = contramap(_ => value) - final def contramap[B](f: B => A): RxWriter[B] = transformRxWriter(_.contramap(f)) - final def contramapIterable[B](f: B => Iterable[A]): RxWriter[B] = transformRxWriter(_.contramapIterable(f)) + def as(value: A): RxWriter[Any] = contramap(_ => value) + def contramap[B](f: B => A): RxWriter[B] = transformRxWriter(_.contramap(f)) + def contramapIterable[B](f: B => Iterable[A]): RxWriter[B] = transformRxWriter(_.contramapIterable(f)) - final def contracollect[B](f: PartialFunction[B, A]): RxWriter[B] = transformRxWriter(_.contracollect(f)) + def contracollect[B](f: PartialFunction[B, A]): RxWriter[B] = transformRxWriter(_.contracollect(f)) - final def transformRxWriter[B](f: Observer[A] => Observer[B]): RxWriter[B] = RxWriter.observer(f(observer)) + def transformRxWriter[B](f: Observer[A] => Observer[B]): RxWriter[B] = RxWriter.observer(f(observer)) } object RxWriter { @@ -301,16 +280,14 @@ object RxWriter { } trait VarEvent[A] extends RxWriter[A] with RxEvent[A] { - final def transformVarEvent[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = + def transformVarEvent[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = VarEvent.combine(g(this), f(this)) - final def transformVarEventRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.combine(g(this), this) - final def transformVarEventRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.combine(this, f(this)) + def transformVarEventRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.combine(g(this), this) + def transformVarEventRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.combine(this, f(this)) } object VarEvent { - def apply[A](): VarEvent[A] = new VarEventSubject(Nil) - - def apply[A](value: A): VarEvent[A] = new VarEventSubject(value :: Nil) + def apply[A](): VarEvent[A] = new VarEventSubject def subject[A](read: Subject[A]): VarEvent[A] = combine(RxEvent.observable(read), RxWriter.observer(read)) @@ -318,44 +295,42 @@ object VarEvent { } trait VarLater[A] extends RxWriter[A] with RxLater[A] { - final def transformVarLater[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = VarLater.combine(g(this), f(this)) - final def transformVarLaterRx(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.combine(g(this), this) - final def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.combine(this, f(this)) + def transformVarLater[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = VarLater.combine(g(this), f(this)) + def transformVarLaterRx(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.combine(g(this), this) + def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.combine(this, f(this)) } object VarLater { - def apply[A](): VarLater[A] = new VarLaterSubject(None) - - def apply[A](value: A): VarLater[A] = new VarLaterSubject(Some(value)) + def apply[A](): VarLater[A] = new VarLaterSubject def subject[A](read: Subject[A]): VarLater[A] = combine(RxLater.observable(read), RxWriter.observer(read)) def combine[A](read: RxLater[A], write: RxWriter[A]): VarLater[A] = new VarLaterCombine(read, write) } -trait Var[A] extends RxWriter[A] with Rx[A] { - final def updateIfSubscribed(f: A => A): Unit = set(f(nowIfSubscribed())) - final def update(f: A => A): Unit = set(f(now())) +trait Var[A] extends VarLater[A] with Rx[A] { + def updateIfSubscribed(f: A => A): Unit = set(f(nowIfSubscribed())) + def update(f: A => A): Unit = set(f(now())) - final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: Rx[A] => Rx[A2]): Var[A2] = Var.combine(g(this), f(this)) - final def transformVarRx(g: Rx[A] => Rx[A]): Var[A] = Var.combine(g(this), this) - final def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.combine(this, f(this)) + def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: Rx[A] => Rx[A2]): Var[A2] = Var.combine(g(this), f(this)) + def transformVarRx(g: Rx[A] => Rx[A]): Var[A] = Var.combine(g(this), this) + def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.combine(this, f(this)) - final def imap[A2](f: A2 => A)(g: A => A2): Var[A2] = transformVar(_.contramap(f))(_.map(g)) - final def lens[B](read: A => B)(write: (A, B) => A): Var[B] = + def imap[A2](f: A2 => A)(g: A => A2): Var[A2] = transformVar(_.contramap(f))(_.map(g)) + def lens[B](read: A => B)(write: (A, B) => A): Var[B] = transformVar(_.contramap(write(nowIfSubscribed(), _)))(_.map(read)) - final def prism[A2](f: A2 => A)(g: A => Option[A2])(seed: => A2): Var[A2] = + def prism[A2](f: A2 => A)(g: A => Option[A2])(seed: => A2): Var[A2] = transformVar(_.contramap(f))(rx => Rx.observableSync(rx.observable.mapFilter(g).prependEval(seed))) - final def subType[A2 <: A: ClassTag](seed: => A2): Var[A2] = prism[A2]((x: A2) => x) { + def subType[A2 <: A: ClassTag](seed: => A2): Var[A2] = prism[A2]((x: A2) => x) { case a: A2 => Some(a) case _ => None }(seed) - final def imapO[B](optic: Iso[A, B]): Var[B] = imap(optic.reverseGet(_))(optic.get(_)) - final def lensO[B](optic: Lens[A, B]): Var[B] = lens(optic.get(_))((base, zoomed) => optic.replace(zoomed)(base)) - final def prismO[B](optic: Prism[A, B])(seed: => B): Var[B] = + def imapO[B](optic: Iso[A, B]): Var[B] = imap(optic.reverseGet(_))(optic.get(_)) + def lensO[B](optic: Lens[A, B]): Var[B] = lens(optic.get(_))((base, zoomed) => optic.replace(zoomed)(base)) + def prismO[B](optic: Prism[A, B])(seed: => B): Var[B] = prism(optic.reverseGet(_))(optic.getOption(_))(seed) } @@ -430,42 +405,39 @@ object Var { } } -private class RxEventObservable[A](val observable: Observable[A]) extends RxEvent[A] - -private object RxEventEmpty extends RxEventObservable(Observable.empty) +private final class RxEventObservable[A](val observable: Observable[A]) extends RxEvent[A] private final object RxLaterEmpty extends RxLater[Nothing] { + private lazy val someNone = Some(None) + val observable = Observable.empty - def nowOption() = Some(None) - def now()(implicit owner: NowOwner) = None - def apply()(implicit owner: LiveOwner) = None + + def nowOptionOpt() = someNone + def nowOpt()(implicit owner: NowOwner) = None + def applyOpt()(implicit owner: LiveOwner) = None } -private final class RxLaterConst[A](value: A) extends RxLater[A] { - val observable = Observable.pure(value) - def nowOption() = Some(Some(value)) - def now()(implicit owner: NowOwner) = Some(value) - def apply()(implicit owner: LiveOwner) = Some(value) +private final class RxConst[A](value: A) extends Rx[A] { + private lazy val someValue = Some(value) + + val observable: Observable[A] = Observable.pure(value) + + def nowOption(): Option[A] = someValue + def now()(implicit owner: NowOwner): A = value + def apply()(implicit owner: LiveOwner): A = value } private final class RxLaterObservable[A](inner: Observable[A]) extends RxLater[A] { - private val state = Rx.observable(inner.map[Option[A]](Some.apply))(None) + private val state = Rx.observableSeed(inner.map[Option[A]](Some.apply))(None) val observable: Observable[A] = state.observable.flattenOption.distinctOnEquals - def nowOption() = state.nowOption() - def now()(implicit owner: NowOwner) = state.now() - def apply()(implicit owner: LiveOwner) = state() -} - -private final class RxConst[A](value: A) extends Rx[A] { - val observable: Observable[A] = Observable.pure(value) - def nowOption(): Option[A] = Some(value) - def now()(implicit owner: NowOwner): A = value - def apply()(implicit owner: LiveOwner): A = value + def nowOptionOpt() = state.nowOption() + def nowOpt()(implicit owner: NowOwner) = state.now() + def applyOpt()(implicit owner: LiveOwner) = state() } -private final class RxObservableSync[A](inner: Observable[A]) extends Rx[A] { +private final class RxSyncObservable[A](inner: Observable[A]) extends Rx[A] { private val state = new ReplayLatestSubject[A]() val observable: Observable[A] = @@ -478,9 +450,9 @@ private final class RxObservableSync[A](inner: Observable[A]) extends Rx[A] { private final class RxWriterObserver[A](val observer: Observer[A]) extends RxWriter[A] -private final class VarEventSubject[A](inits: Iterable[A]) extends VarEvent[A] { +private final class VarEventSubject[A] extends VarEvent[A] { private val state = new PublishSubject[A] - val observable: Observable[A] = state.startWith(inits) + val observable: Observable[A] = state val observer: Observer[A] = state } @@ -489,22 +461,23 @@ private final class VarEventCombine[A](innerRead: RxEvent[A], innerWrite: RxWrit val observer = innerWrite.observer } -private final class VarLaterSubject[A](seed: Option[A]) extends VarLater[A] { - private val state = Var(seed) +private final class VarLaterSubject[A] extends VarLater[A] { + private val state = Var[Option[A]](None) val observable: Observable[A] = state.observable.flattenOption.distinctOnEquals val observer: Observer[A] = state.observer.contramap(Some.apply) - def nowOption() = state.nowOption() - def now()(implicit owner: NowOwner) = state.now() - def apply()(implicit owner: LiveOwner) = state() + def nowOptionOpt() = state.nowOption() + def nowOpt()(implicit owner: NowOwner) = state.now() + def applyOpt()(implicit owner: LiveOwner) = state() } private final class VarLaterCombine[A](innerRead: RxLater[A], innerWrite: RxWriter[A]) extends VarLater[A] { - def nowOption() = innerRead.nowOption() - def now()(implicit owner: NowOwner) = innerRead.now() - def apply()(implicit owner: LiveOwner) = innerRead() val observable = innerRead.observable val observer = innerWrite.observer + + def nowOptionOpt() = innerRead.nowOptionOpt() + def nowOpt()(implicit owner: NowOwner) = innerRead.nowOpt() + def applyOpt()(implicit owner: LiveOwner) = innerRead.applyOpt() } private final class VarSubject[A](seed: A) extends Var[A] { @@ -518,9 +491,10 @@ private final class VarSubject[A](seed: A) ext def apply()(implicit owner: LiveOwner): A = owner.unsafeLive(this) } private final class VarCombine[A](innerRead: Rx[A], innerWrite: RxWriter[A]) extends Var[A] { + val observable = innerRead.observable + val observer = innerWrite.observer + def nowOption() = innerRead.nowOption() def now()(implicit owner: NowOwner) = innerRead.now() def apply()(implicit owner: LiveOwner): A = innerRead() - val observable = innerRead.observable - val observer = innerWrite.observer } From d12cb0b030d1b64d92d56757d877f418ba3ae600 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 16 Jan 2023 13:26:53 +0100 Subject: [PATCH 15/44] wip --- .../scala/colibri/reactive/Reactive.scala | 143 ++++++++---------- .../src/test/scala/colibri/ReactiveSpec.scala | 34 +++-- 2 files changed, 85 insertions(+), 92 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 1985efee..1a7fe7d4 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -14,56 +14,62 @@ object RxMissingNowException extends Exception("Missing current value inside an Rx. Make sure, the Rx has active subscriptions when calling nowIfSubscribed.") trait RxSource[+A] { + type Self[+X] <: RxSource[X] + def observable: Observable[A] + def transformRxSource[B](f: Observable[A] => Observable[B]): Self[B] + final def subscribe: SyncIO[Cancelable] = observable.subscribeSyncIO final def unsafeSubscribe(): Cancelable = observable.unsafeSubscribe() final def unsafeSubscribe(writer: RxWriter[A]): Cancelable = observable.unsafeSubscribe(writer.observer) final def unsafeForeach(f: A => Unit): Cancelable = observable.unsafeForeach(f) -} - -object RxSource { - implicit object source extends Source[RxSource] { - def unsafeSubscribe[A](source: RxSource[A])(sink: Observer[A]): Cancelable = source.observable.unsafeSubscribe(sink) - } -} -trait RxEvent[+A] extends RxSource[A] { - def collect[B](f: PartialFunction[A, B]): RxEvent[B] = transformRxEvent(_.collect(f)) - def map[B](f: A => B): RxEvent[B] = transformRxEvent(_.map(f)) - def mapEither[B](f: A => Either[Throwable, B]): RxEvent[B] = transformRxEvent(_.mapEither(f)) - def tap(f: A => Unit): RxEvent[A] = transformRxEvent(_.tap(f)) + def drop(n: Int): Self[A] = transformRxSource(_.drop(n)) + def collect[B](f: PartialFunction[A, B]): Self[B] = transformRxSource(_.collect(f)) + def map[B](f: A => B): Self[B] = transformRxSource(_.map(f)) + def mapEither[B](f: A => Either[Throwable, B]): Self[B] = transformRxSource(_.mapEither(f)) + def tap(f: A => Unit): Self[A] = transformRxSource(_.tap(f)) - def mapEffect[F[_]: RunEffect, B](f: A => F[B]): RxEvent[B] = transformRxEvent(_.mapEffect(f)) - def mapFuture[B](f: A => Future[B]): RxEvent[B] = transformRxEvent(_.mapFuture(f)) + def mapEffect[F[_]: RunEffect, B](f: A => F[B]): Self[B] = transformRxSource(_.mapEffect(f)) + def mapFuture[B](f: A => Future[B]): Self[B] = transformRxSource(_.mapFuture(f)) - def as[B](value: B): RxEvent[B] = transformRxEvent(_.as(value)) - def asEval[B](value: => B): RxEvent[B] = transformRxEvent(_.asEval(value)) + def as[B](value: B): Self[B] = transformRxSource(_.as(value)) + def asEval[B](value: => B): Self[B] = transformRxSource(_.asEval(value)) - def asEffect[F[_]: RunEffect, B](value: F[B]): RxEvent[B] = transformRxEvent(_.asEffect(value)) - def asFuture[B](value: => Future[B]): RxEvent[B] = transformRxEvent(_.asFuture(value)) + def asEffect[F[_]: RunEffect, B](value: F[B]): Self[B] = transformRxSource(_.asEffect(value)) + def asFuture[B](value: => Future[B]): Self[B] = transformRxSource(_.asFuture(value)) - def via(writer: RxWriter[A]): RxEvent[A] = transformRxEvent(_.via(writer.observer)) + def via(writer: RxWriter[A]): Self[A] = transformRxSource(_.via(writer.observer)) - def switchMap[B](f: A => RxSource[B]): RxEvent[B] = transformRxEvent(_.switchMap(f andThen (_.observable))) - def mergeMap[B](f: A => RxSource[B]): RxEvent[B] = transformRxEvent(_.mergeMap(f andThen (_.observable))) - def concatMap[B](f: A => RxSource[B]): RxEvent[B] = transformRxEvent(_.concatMap(f andThen (_.observable))) + def switchMap[B](f: A => RxSource[B]): Self[B] = transformRxSource(_.switchMap(f andThen (_.observable))) + def mergeMap[B](f: A => RxSource[B]): Self[B] = transformRxSource(_.mergeMap(f andThen (_.observable))) + def concatMap[B](f: A => RxSource[B]): Self[B] = transformRxSource(_.concatMap(f andThen (_.observable))) - def combineLatestMap[B, R](sourceB: RxEvent[B])(f: (A, B) => R): RxEvent[R] = transformRxEvent( + def combineLatestMap[B, R](sourceB: RxSource[B])(f: (A, B) => R): Self[R] = transformRxSource( _.combineLatestMap(sourceB.observable)(f), ) - def combineLatest[B](sourceB: RxEvent[B]): RxEvent[(A, B)] = transformRxEvent(_.combineLatest(sourceB.observable)) + def combineLatest[B](sourceB: RxSource[B]): Self[(A, B)] = transformRxSource(_.combineLatest(sourceB.observable)) - def withLatestMap[B, R](sourceB: RxEvent[B])(f: (A, B) => R): RxEvent[R] = transformRxEvent(_.withLatestMap(sourceB.observable)(f)) - def withLatest[B](sourceB: RxEvent[B]): RxEvent[(A, B)] = transformRxEvent(_.withLatest(sourceB.observable)) + def withLatestMap[B, R](sourceB: RxSource[B])(f: (A, B) => R): Self[R] = transformRxSource(_.withLatestMap(sourceB.observable)(f)) + def withLatest[B](sourceB: RxSource[B]): Self[(A, B)] = transformRxSource(_.withLatest(sourceB.observable)) - def hot: SyncIO[RxEvent[A]] = SyncIO(unsafeHot()) - def unsafeHot(): RxEvent[A] = { val _ = unsafeSubscribe(); this } + def hot: SyncIO[Self[A]] = SyncIO(unsafeHot()) + def unsafeHot(): Self[A] = { val _ = unsafeSubscribe(); transformRxSource(identity) } +} - def transformRxEvent[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) - def transformRxEventUnshared[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observableUnshared(f(observable)) +object RxSource { + implicit object source extends Source[RxSource] { + def unsafeSubscribe[A](source: RxSource[A])(sink: Observer[A]): Cancelable = source.observable.unsafeSubscribe(sink) + } +} + +trait RxEvent[+A] extends RxSource[A] { + type Self[+X] = RxEvent[X] + + final override def transformRxSource[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) } object RxEvent extends RxPlatform { @@ -85,11 +91,11 @@ object RxEvent extends RxPlatform { def observableUnshared[A](observable: Observable[A]): RxEvent[A] = new RxEventObservable(observable) @inline implicit final class RxEventOps[A](private val self: RxEvent[A]) extends AnyVal { - def scan[B](seed: => B)(f: (B, A) => B): RxEvent[B] = self.transformRxEvent(_.scan(seed)(f)) + def scan[B](seed: => B)(f: (B, A) => B): RxEvent[B] = self.transformRxSource(_.scan(seed)(f)) - def filter(f: A => Boolean): RxEvent[A] = self.transformRxEvent(_.filter(f)) + def filter(f: A => Boolean): RxEvent[A] = self.transformRxSource(_.filter(f)) - def prepend(value: A): RxEvent[A] = self.transformRxEvent(_.prepend(value)) + def prepend(value: A): RxEvent[A] = self.transformRxSource(_.prepend(value)) } @inline implicit final class RxEventBooleanOps(private val source: RxEvent[Boolean]) extends AnyVal { @@ -105,6 +111,8 @@ object RxEvent extends RxPlatform { } trait RxLater[+A] extends RxSource[A] { + type Self[+X] = RxLater[X] + def nowOptionOpt(): Option[Option[A]] def applyOpt()(implicit owner: LiveOwner): Option[A] @@ -112,30 +120,7 @@ trait RxLater[+A] extends RxSource[A] { final def nowIfSubscribedOpt(): Option[A] = nowOptionOpt().getOrElse(throw RxMissingNowException) - def collect[B](f: PartialFunction[A, B]): RxLater[B] = transformRxLater(_.collect(f)) - def map[B](f: A => B): RxLater[B] = transformRxLater(_.map(f)) - def mapEither[B](f: A => Either[Throwable, B]): RxLater[B] = transformRxLater(_.mapEither(f)) - def tap(f: A => Unit): RxLater[A] = transformRxLater(_.tap(f)) - def drop(n: Int): RxLater[A] = transformRxLater(_.drop(n)) - - def mapEffect[F[_]: RunEffect, B](f: A => F[B]): RxLater[B] = transformRxLater(_.mapEffect(f)) - def mapFuture[B](f: A => Future[B]): RxLater[B] = transformRxLater(_.mapFuture(f)) - - def as[B](value: B): RxLater[B] = transformRxLater(_.as(value)) - def asEval[B](value: => B): RxLater[B] = transformRxLater(_.asEval(value)) - - def asEffect[F[_]: RunEffect, B](value: F[B]): RxLater[B] = transformRxLater(_.asEffect(value)) - def asFuture[B](value: => Future[B]): RxLater[B] = transformRxLater(_.asFuture(value)) - - def via(writer: RxWriter[A]): RxLater[A] = transformRxLater(_.via(writer.observer)) - - def switchMap[B](f: A => RxSource[B]): RxLater[B] = transformRxLater(_.switchMap(f andThen (_.observable))) - def mergeMap[B](f: A => RxSource[B]): RxLater[B] = transformRxLater(_.mergeMap(f andThen (_.observable))) - - def hot: SyncIO[RxLater[A]] = SyncIO(unsafeHot()) - def unsafeHot(): RxLater[A] = { val _ = unsafeSubscribe(); this } - - def transformRxLater[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) + final override def transformRxSource[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) } object RxLater { @@ -149,9 +134,9 @@ object RxLater { def observable[A](observable: Observable[A]): RxLater[A] = new RxLaterObservable(observable) @inline implicit final class RxLaterOps[A](private val self: RxLater[A]) extends AnyVal { - def scan[B](seed: => B)(f: (B, A) => B): RxLater[B] = self.transformRxLater(_.scan0(seed)(f)) + def scan[B](seed: => B)(f: (B, A) => B): RxLater[B] = self.transformRxSource(_.scan0(seed)(f)) - def filter(f: A => Boolean): RxLater[A] = self.transformRxLater(_.filter(f)) + def filter(f: A => Boolean): RxLater[A] = self.transformRxSource(_.filter(f)) def toRx: Rx[Option[A]] = Rx.observableSeed(self.observable.map[Option[A]](Some.apply))(None) def toRx(seed: => A): Rx[A] = Rx.observableSeed(self.observable)(seed) @@ -175,12 +160,15 @@ trait Rx[+A] extends RxLater[A] { def apply()(implicit owner: LiveOwner): A def now()(implicit owner: NowOwner): A - override def nowOptionOpt(): Option[Option[A]] = Some(nowOption()) + final def nowIfSubscribed(): A = nowOption().getOrElse(throw RxMissingNowException) - override def applyOpt()(implicit owner: LiveOwner): Option[A] = Some(apply()) - override def nowOpt()(implicit owner: NowOwner): Option[A] = Some(now()) + final override def nowOptionOpt(): Option[Option[A]] = Some(nowOption()) - final def nowIfSubscribed(): A = nowOption().getOrElse(throw RxMissingNowException) + final override def applyOpt()(implicit owner: LiveOwner): Option[A] = Some(apply()) + final override def nowOpt()(implicit owner: NowOwner): Option[A] = Some(now()) + + final def transformRxSeed[B](f: Observable[A] => Observable[B])(seed: => B): Rx[B] = Rx.observableSeed(f(observable))(seed) + final def transformRxSync[B](f: Observable[A] => Observable[B]): Rx[B] = Rx.observableSync(f(observable)) override def map[B](f: A => B): Rx[B] = transformRxSync(_.map(f)) override def mapEither[B](f: A => Either[Throwable, B]): Rx[B] = transformRxSync(_.mapEither(f)) @@ -194,9 +182,6 @@ trait Rx[+A] extends RxLater[A] { def mapSyncEffect[F[_]: RunSyncEffect, B](f: A => F[B]): Rx[B] = transformRxSync(_.mapEffect(f)) def asSyncEffect[F[_]: RunSyncEffect, B](value: F[B]): Rx[B] = transformRxSync(_.asEffect(value)) - - def transformRxSeed[B](f: Observable[A] => Observable[B])(seed: => B): Rx[B] = Rx.observableSeed(f(observable))(seed) - def transformRxSync[B](f: Observable[A] => Observable[B]): Rx[B] = Rx.observableSync(f(observable)) } object Rx extends RxPlatform { @@ -250,7 +235,7 @@ object Rx extends RxPlatform { trait RxWriter[-A] { def observer: Observer[A] - def set(value: A): Unit = observer.unsafeOnNext(value) + final def set(value: A): Unit = observer.unsafeOnNext(value) def as(value: A): RxWriter[Any] = contramap(_ => value) def contramap[B](f: B => A): RxWriter[B] = transformRxWriter(_.contramap(f)) @@ -258,7 +243,7 @@ trait RxWriter[-A] { def contracollect[B](f: PartialFunction[B, A]): RxWriter[B] = transformRxWriter(_.contracollect(f)) - def transformRxWriter[B](f: Observer[A] => Observer[B]): RxWriter[B] = RxWriter.observer(f(observer)) + final def transformRxWriter[B](f: Observer[A] => Observer[B]): RxWriter[B] = RxWriter.observer(f(observer)) } object RxWriter { @@ -280,10 +265,10 @@ object RxWriter { } trait VarEvent[A] extends RxWriter[A] with RxEvent[A] { - def transformVarEvent[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = + final def transformVarEvent[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = VarEvent.combine(g(this), f(this)) - def transformVarEventRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.combine(g(this), this) - def transformVarEventRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.combine(this, f(this)) + final def transformVarEventRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.combine(g(this), this) + final def transformVarEventRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.combine(this, f(this)) } object VarEvent { @@ -295,9 +280,9 @@ object VarEvent { } trait VarLater[A] extends RxWriter[A] with RxLater[A] { - def transformVarLater[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = VarLater.combine(g(this), f(this)) - def transformVarLaterRx(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.combine(g(this), this) - def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.combine(this, f(this)) + final def transformVarLater[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = VarLater.combine(g(this), f(this)) + final def transformVarLaterRx(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.combine(g(this), this) + final def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.combine(this, f(this)) } object VarLater { @@ -309,12 +294,12 @@ object VarLater { } trait Var[A] extends VarLater[A] with Rx[A] { - def updateIfSubscribed(f: A => A): Unit = set(f(nowIfSubscribed())) - def update(f: A => A): Unit = set(f(now())) + final def updateIfSubscribed(f: A => A): Unit = set(f(nowIfSubscribed())) + final def update(f: A => A): Unit = set(f(now())) - def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: Rx[A] => Rx[A2]): Var[A2] = Var.combine(g(this), f(this)) - def transformVarRx(g: Rx[A] => Rx[A]): Var[A] = Var.combine(g(this), this) - def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.combine(this, f(this)) + final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: Rx[A] => Rx[A2]): Var[A2] = Var.combine(g(this), f(this)) + final def transformVarRx(g: Rx[A] => Rx[A]): Var[A] = Var.combine(g(this), this) + final def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.combine(this, f(this)) def imap[A2](f: A2 => A)(g: A => A2): Var[A2] = transformVar(_.contramap(f))(_.map(g)) def lens[B](read: A => B)(write: (A, B) => A): Var[B] = diff --git a/reactive/src/test/scala/colibri/ReactiveSpec.scala b/reactive/src/test/scala/colibri/ReactiveSpec.scala index 1e97f8b1..799d733e 100644 --- a/reactive/src/test/scala/colibri/ReactiveSpec.scala +++ b/reactive/src/test/scala/colibri/ReactiveSpec.scala @@ -592,7 +592,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { it should "collect" in { val variable = Var[Option[Int]](Some(1)) - val collected = variable.collect { case Some(x) => x }(0) + val collected = variable.collect { case Some(x) => x }.toRx(0) var collectedStates = Vector.empty[Int] collected.unsafeForeach(collectedStates :+= _) @@ -609,7 +609,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { it should "collect initial none" in { val variable = Var[Option[Int]](None) - val collected = variable.collect { case Some(x) => x }(0) + val collected = variable.collect { case Some(x) => x }.toRx(0) var collectedStates = Vector.empty[Int] collected.unsafeForeach(collectedStates :+= _) @@ -832,31 +832,31 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { mapped.nowOption() shouldBe None } - it should "tapLater" in { + it should "drop" in { var triggers1 = List.empty[Int] val variable1 = Var(1) - val variable1Logged = variable1.tapLater(triggers1 ::= _) + val variable1Logged = variable1.drop(1).tap(triggers1 ::= _) triggers1 shouldBe List.empty - variable1Logged.nowOption() shouldBe None + variable1Logged.nowOptionOpt() shouldBe None triggers1 shouldBe List.empty val cancelable = variable1Logged.unsafeSubscribe() triggers1 shouldBe List.empty - variable1Logged.nowOption() shouldBe Some(1) + variable1Logged.nowOptionOpt() shouldBe Some(None) triggers1 shouldBe List.empty variable1.set(2) triggers1 shouldBe List(2) - variable1Logged.nowOption() shouldBe Some(2) + variable1Logged.nowOptionOpt() shouldBe Some(Some(2)) triggers1 shouldBe List(2) variable1.set(3) triggers1 shouldBe List(3, 2) - variable1Logged.nowOption() shouldBe Some(3) + variable1Logged.nowOptionOpt() shouldBe Some(Some(3)) triggers1 shouldBe List(3, 2) cancelable.unsafeCancel() @@ -864,19 +864,19 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { variable1.set(4) triggers1 shouldBe List(3, 2) - variable1Logged.nowOption() shouldBe None + variable1Logged.nowOptionOpt() shouldBe None triggers1 shouldBe List(3, 2) - variable1Logged.now() shouldBe 4 + variable1Logged.nowOpt() shouldBe Some(4) triggers1 shouldBe List(3, 2) variable1.set(5) triggers1 shouldBe List(3, 2) - variable1Logged.nowOption() shouldBe None + variable1Logged.nowOptionOpt() shouldBe None triggers1 shouldBe List(3, 2) - variable1Logged.now() shouldBe 5 + variable1Logged.nowOpt() shouldBe Some(5) triggers1 shouldBe List(3, 2) } @@ -1061,7 +1061,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { var results1 = List.empty[Int] var results2 = List.empty[Int] - val variable1 = VarEvent(100) + val variable1 = VarEvent[Int]() val rx2 = RxEvent(1, 2) val variable1Logged = variable1.tap(triggers1 ::= _) val rx2Logged = rx2.tap(triggers2 ::= _) @@ -1080,6 +1080,14 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { val cancelable1 = mapped.unsafeForeach(results1 ::= _) + triggers1 shouldBe List.empty + triggers2 shouldBe List(2, 1) + triggerRxCount shouldBe 0 + results1 shouldBe List.empty + results2 shouldBe List.empty + + variable1.set(100) + triggers1 shouldBe List(100) triggers2 shouldBe List(2, 1) triggerRxCount shouldBe 2 From 8cedd91f95974e52739307283d9d5c193c5d9bac Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 16 Jan 2023 13:26:57 +0100 Subject: [PATCH 16/44] wip --- .../scala/colibri/reactive/Reactive.scala | 231 ++++++++---------- .../src/test/scala/colibri/ReactiveSpec.scala | 62 ++--- 2 files changed, 139 insertions(+), 154 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 1a7fe7d4..564312db 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -15,61 +15,91 @@ object RxMissingNowException trait RxSource[+A] { type Self[+X] <: RxSource[X] + type SelfSync[+X] <: RxSource[X] def observable: Observable[A] + def selfRxSourceSync: SelfSync[A] def transformRxSource[B](f: Observable[A] => Observable[B]): Self[B] + def transformRxSourceSync[B](f: Observable[A] => Observable[B]): SelfSync[B] final def subscribe: SyncIO[Cancelable] = observable.subscribeSyncIO final def unsafeSubscribe(): Cancelable = observable.unsafeSubscribe() final def unsafeSubscribe(writer: RxWriter[A]): Cancelable = observable.unsafeSubscribe(writer.observer) - final def unsafeForeach(f: A => Unit): Cancelable = observable.unsafeForeach(f) - def drop(n: Int): Self[A] = transformRxSource(_.drop(n)) - def collect[B](f: PartialFunction[A, B]): Self[B] = transformRxSource(_.collect(f)) - def map[B](f: A => B): Self[B] = transformRxSource(_.map(f)) - def mapEither[B](f: A => Either[Throwable, B]): Self[B] = transformRxSource(_.mapEither(f)) - def tap(f: A => Unit): Self[A] = transformRxSource(_.tap(f)) + final def hot: SyncIO[SelfSync[A]] = SyncIO(unsafeHot()) + final def unsafeHot(): SelfSync[A] = { + val _ = unsafeSubscribe() + selfRxSourceSync + } + + final def map[B](f: A => B): SelfSync[B] = transformRxSourceSync(_.map(f)) + final def tap(f: A => Unit): SelfSync[A] = transformRxSourceSync(_.tap(f)) - def mapEffect[F[_]: RunEffect, B](f: A => F[B]): Self[B] = transformRxSource(_.mapEffect(f)) - def mapFuture[B](f: A => Future[B]): Self[B] = transformRxSource(_.mapFuture(f)) + final def collect[B](f: PartialFunction[A, B]): Self[B] = transformRxSource(_.collect(f)) + final def mapEither[B](f: A => Either[Throwable, B]): Self[B] = transformRxSource(_.mapEither(f)) + final def drop(n: Int): Self[A] = transformRxSource(_.drop(n)) - def as[B](value: B): Self[B] = transformRxSource(_.as(value)) - def asEval[B](value: => B): Self[B] = transformRxSource(_.asEval(value)) + final def mapSyncEffect[F[_]: RunSyncEffect, B](f: A => F[B]): SelfSync[B] = transformRxSourceSync(_.mapEffect(f)) + final def mapEffect[F[_]: RunEffect, B](f: A => F[B]): Self[B] = transformRxSource(_.mapEffect(f)) + final def mapFuture[B](f: A => Future[B]): Self[B] = transformRxSource(_.mapFuture(f)) - def asEffect[F[_]: RunEffect, B](value: F[B]): Self[B] = transformRxSource(_.asEffect(value)) - def asFuture[B](value: => Future[B]): Self[B] = transformRxSource(_.asFuture(value)) + final def as[B](value: B): SelfSync[B] = transformRxSourceSync(_.as(value)) + final def asEval[B](value: => B): SelfSync[B] = transformRxSourceSync(_.asEval(value)) - def via(writer: RxWriter[A]): Self[A] = transformRxSource(_.via(writer.observer)) + final def asSyncEffect[F[_]: RunSyncEffect, B](value: F[B]): SelfSync[B] = transformRxSourceSync(_.asEffect(value)) + final def asEffect[F[_]: RunEffect, B](value: F[B]): Self[B] = transformRxSource(_.asEffect(value)) + final def asFuture[B](value: => Future[B]): Self[B] = transformRxSource(_.asFuture(value)) - def switchMap[B](f: A => RxSource[B]): Self[B] = transformRxSource(_.switchMap(f andThen (_.observable))) - def mergeMap[B](f: A => RxSource[B]): Self[B] = transformRxSource(_.mergeMap(f andThen (_.observable))) - def concatMap[B](f: A => RxSource[B]): Self[B] = transformRxSource(_.concatMap(f andThen (_.observable))) + final def via(writer: RxWriter[A]): SelfSync[A] = transformRxSourceSync(_.via(writer.observer)) - def combineLatestMap[B, R](sourceB: RxSource[B])(f: (A, B) => R): Self[R] = transformRxSource( - _.combineLatestMap(sourceB.observable)(f), - ) - def combineLatest[B](sourceB: RxSource[B]): Self[(A, B)] = transformRxSource(_.combineLatest(sourceB.observable)) + final def switchMap[B](f: A => RxSource[B]): Self[B] = transformRxSource(_.switchMap(f andThen (_.observable))) + final def mergeMap[B](f: A => RxSource[B]): Self[B] = transformRxSource(_.mergeMap(f andThen (_.observable))) + final def concatMap[B](f: A => RxSource[B]): Self[B] = transformRxSource(_.concatMap(f andThen (_.observable))) - def withLatestMap[B, R](sourceB: RxSource[B])(f: (A, B) => R): Self[R] = transformRxSource(_.withLatestMap(sourceB.observable)(f)) - def withLatest[B](sourceB: RxSource[B]): Self[(A, B)] = transformRxSource(_.withLatest(sourceB.observable)) + final def combineLatestMap[B, R](sourceB: RxSource[B])(f: (A, B) => R): Self[R] = + transformRxSource(_.combineLatestMap(sourceB.observable)(f)) + final def combineLatest[B](sourceB: RxSource[B]): Self[(A, B)] = + transformRxSource(_.combineLatest(sourceB.observable)) - def hot: SyncIO[Self[A]] = SyncIO(unsafeHot()) - def unsafeHot(): Self[A] = { val _ = unsafeSubscribe(); transformRxSource(identity) } + final def withLatestMap[B, R](sourceB: RxSource[B])(f: (A, B) => R): Self[R] = + transformRxSource(_.withLatestMap(sourceB.observable)(f)) + final def withLatest[B](sourceB: RxSource[B]): Self[(A, B)] = + transformRxSource(_.withLatest(sourceB.observable)) } object RxSource { implicit object source extends Source[RxSource] { def unsafeSubscribe[A](source: RxSource[A])(sink: Observer[A]): Cancelable = source.observable.unsafeSubscribe(sink) } + + @inline implicit final class RxSourceOps[A](val self: RxSource[A]) extends AnyVal { + def scan[B](seed: => B)(f: (B, A) => B): self.SelfSync[B] = self.transformRxSourceSync(_.scan(seed)(f)) + + def filter(f: A => Boolean): self.Self[A] = self.transformRxSource(_.filter(f)) + } + + @inline implicit final class RxSourceBooleanOps(val self: RxSource[Boolean]) extends AnyVal { + @inline def toggle[A](ifTrue: => A, ifFalse: => A): self.SelfSync[A] = self.map { + case true => ifTrue + case false => ifFalse + } + + @inline def toggle[A: Monoid](ifTrue: => A): self.SelfSync[A] = toggle(ifTrue, Monoid[A].empty) + + @inline def negated: self.SelfSync[Boolean] = self.map(x => !x) + } } trait RxEvent[+A] extends RxSource[A] { type Self[+X] = RxEvent[X] + type SelfSync[+X] = RxEvent[X] + final override def selfRxSourceSync: RxEvent[A] = this final override def transformRxSource[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) + final override def transformRxSourceSync[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) } object RxEvent extends RxPlatform { @@ -87,74 +117,48 @@ object RxEvent extends RxPlatform { def switch[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.switchIterable(rxs.map(_.observable))) def concat[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.concatIterable(rxs.map(_.observable))) - def observable[A](observable: Observable[A]): RxEvent[A] = new RxEventObservable(observable.publish.refCount) - def observableUnshared[A](observable: Observable[A]): RxEvent[A] = new RxEventObservable(observable) - - @inline implicit final class RxEventOps[A](private val self: RxEvent[A]) extends AnyVal { - def scan[B](seed: => B)(f: (B, A) => B): RxEvent[B] = self.transformRxSource(_.scan(seed)(f)) - - def filter(f: A => Boolean): RxEvent[A] = self.transformRxSource(_.filter(f)) - - def prepend(value: A): RxEvent[A] = self.transformRxSource(_.prepend(value)) - } - - @inline implicit final class RxEventBooleanOps(private val source: RxEvent[Boolean]) extends AnyVal { - @inline def toggle[A](ifTrue: => A, ifFalse: => A): RxEvent[A] = source.map { - case true => ifTrue - case false => ifFalse - } + def observable[A](observable: Observable[A]): RxEvent[A] = observableUnshared(observable.publish.refCount) + private def observableUnshared[A](observable: Observable[A]): RxEvent[A] = new RxEventObservable(observable) +} - @inline def toggle[A: Monoid](ifTrue: => A): RxEvent[A] = toggle(ifTrue, Monoid[A].empty) +trait RxState[+A] extends RxSource[A] { + type Self[+X] = RxLater[X] + type SelfSync[+X] <: RxState[X] - @inline def negated: RxEvent[Boolean] = source.map(x => !x) - } + final override def transformRxSource[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) } -trait RxLater[+A] extends RxSource[A] { - type Self[+X] = RxLater[X] +trait RxLater[+A] extends RxState[A] { + type SelfSync[+X] = RxLater[X] - def nowOptionOpt(): Option[Option[A]] + def nowOption(): Option[Option[A]] - def applyOpt()(implicit owner: LiveOwner): Option[A] - def nowOpt()(implicit owner: NowOwner): Option[A] + def apply()(implicit owner: LiveOwner): Option[A] + def now()(implicit owner: NowOwner): Option[A] - final def nowIfSubscribedOpt(): Option[A] = nowOptionOpt().getOrElse(throw RxMissingNowException) + final def nowIfSubscribedOpt(): Option[A] = nowOption().getOrElse(throw RxMissingNowException) - final override def transformRxSource[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) + final override def selfRxSourceSync: RxLater[A] = this + final override def transformRxSourceSync[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) } object RxLater { def empty[A]: RxLater[A] = RxLaterEmpty - def const[A](value: A): RxLater[A] = new RxConst(value) - def future[A](future: => Future[A]): RxLater[A] = observable(Observable.fromFuture(future)) def effect[F[_]: RunEffect, A](effect: F[A]): RxLater[A] = observable(Observable.fromEffect(effect)) def observable[A](observable: Observable[A]): RxLater[A] = new RxLaterObservable(observable) @inline implicit final class RxLaterOps[A](private val self: RxLater[A]) extends AnyVal { - def scan[B](seed: => B)(f: (B, A) => B): RxLater[B] = self.transformRxSource(_.scan0(seed)(f)) - - def filter(f: A => Boolean): RxLater[A] = self.transformRxSource(_.filter(f)) - def toRx: Rx[Option[A]] = Rx.observableSeed(self.observable.map[Option[A]](Some.apply))(None) def toRx(seed: => A): Rx[A] = Rx.observableSeed(self.observable)(seed) } - - @inline implicit final class RxLaterBooleanOps(private val source: RxLater[Boolean]) extends AnyVal { - @inline def toggle[A](ifTrue: => A, ifFalse: => A): RxLater[A] = source.map { - case true => ifTrue - case false => ifFalse - } - - @inline def toggle[A: Monoid](ifTrue: => A): RxLater[A] = toggle(ifTrue, Monoid[A].empty) - - @inline def negated: RxLater[Boolean] = source.map(x => !x) - } } -trait Rx[+A] extends RxLater[A] { +trait Rx[+A] extends RxState[A] { + type SelfSync[+X] = Rx[X] + def nowOption(): Option[A] def apply()(implicit owner: LiveOwner): A @@ -162,26 +166,8 @@ trait Rx[+A] extends RxLater[A] { final def nowIfSubscribed(): A = nowOption().getOrElse(throw RxMissingNowException) - final override def nowOptionOpt(): Option[Option[A]] = Some(nowOption()) - - final override def applyOpt()(implicit owner: LiveOwner): Option[A] = Some(apply()) - final override def nowOpt()(implicit owner: NowOwner): Option[A] = Some(now()) - - final def transformRxSeed[B](f: Observable[A] => Observable[B])(seed: => B): Rx[B] = Rx.observableSeed(f(observable))(seed) - final def transformRxSync[B](f: Observable[A] => Observable[B]): Rx[B] = Rx.observableSync(f(observable)) - - override def map[B](f: A => B): Rx[B] = transformRxSync(_.map(f)) - override def mapEither[B](f: A => Either[Throwable, B]): Rx[B] = transformRxSync(_.mapEither(f)) - override def tap(f: A => Unit): Rx[A] = transformRxSync(_.tap(f)) - override def as[B](value: B): Rx[B] = transformRxSync(_.as(value)) - override def asEval[B](value: => B): Rx[B] = transformRxSync(_.asEval(value)) - override def via(writer: RxWriter[A]): Rx[A] = transformRxSync(_.via(writer.observer)) - override def hot: SyncIO[Rx[A]] = SyncIO(unsafeHot()) - override def unsafeHot(): Rx[A] = { val _ = unsafeSubscribe(); this } - - def mapSyncEffect[F[_]: RunSyncEffect, B](f: A => F[B]): Rx[B] = transformRxSync(_.mapEffect(f)) - - def asSyncEffect[F[_]: RunSyncEffect, B](value: F[B]): Rx[B] = transformRxSync(_.asEffect(value)) + final override def selfRxSourceSync: Rx[A] = this + final override def transformRxSourceSync[B](f: Observable[A] => Observable[B]): Rx[B] = Rx.observableSync(f(observable)) } object Rx extends RxPlatform { @@ -214,21 +200,8 @@ object Rx extends RxPlatform { def observableSync[A](observable: Observable[A]): Rx[A] = new RxSyncObservable(observable) - @inline implicit final class RxOps[A](private val self: Rx[A]) extends AnyVal { - def scanReduce(f: (A, A) => A): Rx[A] = scan(self.nowIfSubscribed())(f) - - def scan[B](seed: => B)(f: (B, A) => B): Rx[B] = self.transformRxSync(_.scan0(seed)(f)) - } - - @inline implicit final class RxBooleanOps(private val source: Rx[Boolean]) extends AnyVal { - @inline def toggle[A](ifTrue: => A, ifFalse: => A): Rx[A] = source.map { - case true => ifTrue - case false => ifFalse - } - - @inline def toggle[A: Monoid](ifTrue: => A): Rx[A] = toggle(ifTrue, Monoid[A].empty) - - @inline def negated: Rx[Boolean] = source.map(x => !x) + @inline implicit final class RxLaterOps[A](private val self: Rx[A]) extends AnyVal { + def toRxLater: RxLater[A] = new RxLaterWrap(self) } } @@ -237,11 +210,11 @@ trait RxWriter[-A] { final def set(value: A): Unit = observer.unsafeOnNext(value) - def as(value: A): RxWriter[Any] = contramap(_ => value) - def contramap[B](f: B => A): RxWriter[B] = transformRxWriter(_.contramap(f)) - def contramapIterable[B](f: B => Iterable[A]): RxWriter[B] = transformRxWriter(_.contramapIterable(f)) + final def as(value: A): RxWriter[Any] = contramap(_ => value) + final def contramap[B](f: B => A): RxWriter[B] = transformRxWriter(_.contramap(f)) + final def contramapIterable[B](f: B => Iterable[A]): RxWriter[B] = transformRxWriter(_.contramapIterable(f)) - def contracollect[B](f: PartialFunction[B, A]): RxWriter[B] = transformRxWriter(_.contracollect(f)) + final def contracollect[B](f: PartialFunction[B, A]): RxWriter[B] = transformRxWriter(_.contracollect(f)) final def transformRxWriter[B](f: Observer[A] => Observer[B]): RxWriter[B] = RxWriter.observer(f(observer)) } @@ -279,7 +252,9 @@ object VarEvent { def combine[A](read: RxEvent[A], write: RxWriter[A]): VarEvent[A] = new VarEventCombine(read, write) } -trait VarLater[A] extends RxWriter[A] with RxLater[A] { +trait VarState[A] extends RxWriter[A] with RxState[A] + +trait VarLater[A] extends VarState[A] with RxLater[A] { final def transformVarLater[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = VarLater.combine(g(this), f(this)) final def transformVarLaterRx(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.combine(g(this), this) final def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.combine(this, f(this)) @@ -293,7 +268,7 @@ object VarLater { def combine[A](read: RxLater[A], write: RxWriter[A]): VarLater[A] = new VarLaterCombine(read, write) } -trait Var[A] extends VarLater[A] with Rx[A] { +trait Var[A] extends VarState[A] with Rx[A] { final def updateIfSubscribed(f: A => A): Unit = set(f(nowIfSubscribed())) final def update(f: A => A): Unit = set(f(now())) @@ -397,9 +372,9 @@ private final object RxLaterEmpty extends RxLater[Nothing] { val observable = Observable.empty - def nowOptionOpt() = someNone - def nowOpt()(implicit owner: NowOwner) = None - def applyOpt()(implicit owner: LiveOwner) = None + def nowOption() = someNone + def now()(implicit owner: NowOwner) = None + def apply()(implicit owner: LiveOwner) = None } private final class RxConst[A](value: A) extends Rx[A] { @@ -412,14 +387,22 @@ private final class RxConst[A](value: A) extends Rx[A] { def apply()(implicit owner: LiveOwner): A = value } +private final class RxLaterWrap[A](state: Rx[A]) extends RxLater[A] { + val observable: Observable[A] = state.observable + + def nowOption() = Some(state.nowOption()) + def now()(implicit owner: NowOwner) = Some(state.now()) + def apply()(implicit owner: LiveOwner) = Some(state()) +} + private final class RxLaterObservable[A](inner: Observable[A]) extends RxLater[A] { private val state = Rx.observableSeed(inner.map[Option[A]](Some.apply))(None) - val observable: Observable[A] = state.observable.flattenOption.distinctOnEquals + val observable: Observable[A] = state.observable.flattenOption - def nowOptionOpt() = state.nowOption() - def nowOpt()(implicit owner: NowOwner) = state.now() - def applyOpt()(implicit owner: LiveOwner) = state() + def nowOption() = state.nowOption() + def now()(implicit owner: NowOwner) = state.now() + def apply()(implicit owner: LiveOwner) = state() } private final class RxSyncObservable[A](inner: Observable[A]) extends Rx[A] { @@ -449,20 +432,21 @@ private final class VarEventCombine[A](innerRead: RxEvent[A], innerWrite: RxWrit private final class VarLaterSubject[A] extends VarLater[A] { private val state = Var[Option[A]](None) - val observable: Observable[A] = state.observable.flattenOption.distinctOnEquals + val observable: Observable[A] = state.observable.flattenOption val observer: Observer[A] = state.observer.contramap(Some.apply) - def nowOptionOpt() = state.nowOption() - def nowOpt()(implicit owner: NowOwner) = state.now() - def applyOpt()(implicit owner: LiveOwner) = state() + def nowOption() = state.nowOption() + def now()(implicit owner: NowOwner) = state.now() + def apply()(implicit owner: LiveOwner) = state() } + private final class VarLaterCombine[A](innerRead: RxLater[A], innerWrite: RxWriter[A]) extends VarLater[A] { val observable = innerRead.observable val observer = innerWrite.observer - def nowOptionOpt() = innerRead.nowOptionOpt() - def nowOpt()(implicit owner: NowOwner) = innerRead.nowOpt() - def applyOpt()(implicit owner: LiveOwner) = innerRead.applyOpt() + def nowOption() = innerRead.nowOption() + def now()(implicit owner: NowOwner) = innerRead.now() + def apply()(implicit owner: LiveOwner) = innerRead.apply() } private final class VarSubject[A](seed: A) extends Var[A] { @@ -475,6 +459,7 @@ private final class VarSubject[A](seed: A) ext def now()(implicit owner: NowOwner) = state.now() def apply()(implicit owner: LiveOwner): A = owner.unsafeLive(this) } + private final class VarCombine[A](innerRead: Rx[A], innerWrite: RxWriter[A]) extends Var[A] { val observable = innerRead.observable val observer = innerWrite.observer diff --git a/reactive/src/test/scala/colibri/ReactiveSpec.scala b/reactive/src/test/scala/colibri/ReactiveSpec.scala index 799d733e..fb09a033 100644 --- a/reactive/src/test/scala/colibri/ReactiveSpec.scala +++ b/reactive/src/test/scala/colibri/ReactiveSpec.scala @@ -838,25 +838,25 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { val variable1Logged = variable1.drop(1).tap(triggers1 ::= _) triggers1 shouldBe List.empty - variable1Logged.nowOptionOpt() shouldBe None + variable1Logged.nowOption() shouldBe None triggers1 shouldBe List.empty val cancelable = variable1Logged.unsafeSubscribe() triggers1 shouldBe List.empty - variable1Logged.nowOptionOpt() shouldBe Some(None) + variable1Logged.nowOption() shouldBe Some(None) triggers1 shouldBe List.empty variable1.set(2) triggers1 shouldBe List(2) - variable1Logged.nowOptionOpt() shouldBe Some(Some(2)) + variable1Logged.nowOption() shouldBe Some(Some(2)) triggers1 shouldBe List(2) variable1.set(3) triggers1 shouldBe List(3, 2) - variable1Logged.nowOptionOpt() shouldBe Some(Some(3)) + variable1Logged.nowOption() shouldBe Some(Some(3)) triggers1 shouldBe List(3, 2) cancelable.unsafeCancel() @@ -864,19 +864,19 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { variable1.set(4) triggers1 shouldBe List(3, 2) - variable1Logged.nowOptionOpt() shouldBe None + variable1Logged.nowOption() shouldBe None triggers1 shouldBe List(3, 2) - variable1Logged.nowOpt() shouldBe Some(4) + variable1Logged.now() shouldBe None triggers1 shouldBe List(3, 2) variable1.set(5) triggers1 shouldBe List(3, 2) - variable1Logged.nowOptionOpt() shouldBe None + variable1Logged.nowOption() shouldBe None triggers1 shouldBe List(3, 2) - variable1Logged.nowOpt() shouldBe Some(5) + variable1Logged.now() shouldBe None triggers1 shouldBe List(3, 2) } @@ -1090,48 +1090,48 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { triggers1 shouldBe List(100) triggers2 shouldBe List(2, 1) - triggerRxCount shouldBe 2 - results1 shouldBe List(102, 101) + triggerRxCount shouldBe 1 + results1 shouldBe List(102) results2 shouldBe List.empty variable1.set(200) triggers1 shouldBe List(200, 100) triggers2 shouldBe List(2, 1) - triggerRxCount shouldBe 3 - results1 shouldBe List(202, 102, 101) + triggerRxCount shouldBe 2 + results1 shouldBe List(202, 102) results2 shouldBe List.empty val cancelable2 = mapped.unsafeForeach(results2 ::= _) triggers1 shouldBe List(200, 100) triggers2 shouldBe List(2, 1) - triggerRxCount shouldBe 3 - results1 shouldBe List(202, 102, 101) + triggerRxCount shouldBe 2 + results1 shouldBe List(202, 102) results2 shouldBe List.empty variable1.set(300) triggers1 shouldBe List(300, 200, 100) triggers2 shouldBe List(2, 1) - triggerRxCount shouldBe 4 - results1 shouldBe List(302, 202, 102, 101) + triggerRxCount shouldBe 3 + results1 shouldBe List(302, 202, 102) results2 shouldBe List(302) cancelable1.unsafeCancel() triggers1 shouldBe List(300, 200, 100) triggers2 shouldBe List(2, 1) - triggerRxCount shouldBe 4 - results1 shouldBe List(302, 202, 102, 101) + triggerRxCount shouldBe 3 + results1 shouldBe List(302, 202, 102) results2 shouldBe List(302) variable1.set(400) triggers1 shouldBe List(400, 300, 200, 100) triggers2 shouldBe List(2, 1) - triggerRxCount shouldBe 5 - results1 shouldBe List(302, 202, 102, 101) + triggerRxCount shouldBe 4 + results1 shouldBe List(302, 202, 102) results2 shouldBe List(402, 302) cancelable2.unsafeCancel() @@ -1139,33 +1139,33 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { triggers1 shouldBe List(400, 300, 200, 100) triggers2 shouldBe List(2, 1) - triggerRxCount shouldBe 5 - results1 shouldBe List(302, 202, 102, 101) + triggerRxCount shouldBe 4 + results1 shouldBe List(302, 202, 102) results2 shouldBe List(402, 302) val cancelable1b = mapped.unsafeForeach(results1 ::= _) - triggers1 shouldBe List(100, 400, 300, 200, 100) + triggers1 shouldBe List(400, 300, 200, 100) triggers2 shouldBe List(2, 1, 2, 1) - triggerRxCount shouldBe 7 - results1 shouldBe List(102, 101, 302, 202, 102, 101) + triggerRxCount shouldBe 4 + results1 shouldBe List(302, 202, 102) results2 shouldBe List(402, 302) variable1.set(1000) - triggers1 shouldBe List(1000, 100, 400, 300, 200, 100) + triggers1 shouldBe List(1000, 400, 300, 200, 100) triggers2 shouldBe List(2, 1, 2, 1) - triggerRxCount shouldBe 8 - results1 shouldBe List(1002, 102, 101, 302, 202, 102, 101) + triggerRxCount shouldBe 5 + results1 shouldBe List(1002, 302, 202, 102) results2 shouldBe List(402, 302) cancelable1b.unsafeCancel() variable1.set(2000) - triggers1 shouldBe List(1000, 100, 400, 300, 200, 100) + triggers1 shouldBe List(1000, 400, 300, 200, 100) triggers2 shouldBe List(2, 1, 2, 1) - triggerRxCount shouldBe 8 - results1 shouldBe List(1002, 102, 101, 302, 202, 102, 101) + triggerRxCount shouldBe 5 + results1 shouldBe List(1002, 302, 202, 102) results2 shouldBe List(402, 302) } } From bc4fe34181820320b49839f81ec352857f0f9338 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 16 Jan 2023 13:37:26 +0100 Subject: [PATCH 17/44] rename --- .../scala/colibri/reactive/Reactive.scala | 40 ++++++----- .../src/test/scala/colibri/ReactiveSpec.scala | 68 +++++++++---------- 2 files changed, 53 insertions(+), 55 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 564312db..beebc558 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -11,7 +11,7 @@ import scala.reflect.ClassTag import scala.util.control.NonFatal object RxMissingNowException - extends Exception("Missing current value inside an Rx. Make sure, the Rx has active subscriptions when calling nowIfSubscribed.") + extends Exception("Missing current value inside an RxState (Rx or RxLater). Make sure, the RxState has active subscriptions when calling nowIfSubscribed.") trait RxSource[+A] { type Self[+X] <: RxSource[X] @@ -131,12 +131,11 @@ trait RxState[+A] extends RxSource[A] { trait RxLater[+A] extends RxState[A] { type SelfSync[+X] = RxLater[X] - def nowOption(): Option[Option[A]] - def apply()(implicit owner: LiveOwner): Option[A] def now()(implicit owner: NowOwner): Option[A] + def nowIfSubscribedOption(): Option[Option[A]] - final def nowIfSubscribedOpt(): Option[A] = nowOption().getOrElse(throw RxMissingNowException) + final def nowIfSubscribedOpt(): Option[A] = nowIfSubscribedOption().getOrElse(throw RxMissingNowException) final override def selfRxSourceSync: RxLater[A] = this final override def transformRxSourceSync[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) @@ -159,12 +158,11 @@ object RxLater { trait Rx[+A] extends RxState[A] { type SelfSync[+X] = Rx[X] - def nowOption(): Option[A] - def apply()(implicit owner: LiveOwner): A def now()(implicit owner: NowOwner): A + def nowIfSubscribedOption(): Option[A] - final def nowIfSubscribed(): A = nowOption().getOrElse(throw RxMissingNowException) + final def nowIfSubscribed(): A = nowIfSubscribedOption().getOrElse(throw RxMissingNowException) final override def selfRxSourceSync: Rx[A] = this final override def transformRxSourceSync[B](f: Observable[A] => Observable[B]): Rx[B] = Rx.observableSync(f(observable)) @@ -372,9 +370,9 @@ private final object RxLaterEmpty extends RxLater[Nothing] { val observable = Observable.empty - def nowOption() = someNone - def now()(implicit owner: NowOwner) = None def apply()(implicit owner: LiveOwner) = None + def now()(implicit owner: NowOwner) = None + def nowIfSubscribedOption() = someNone } private final class RxConst[A](value: A) extends Rx[A] { @@ -382,17 +380,17 @@ private final class RxConst[A](value: A) extends Rx[A] { val observable: Observable[A] = Observable.pure(value) - def nowOption(): Option[A] = someValue - def now()(implicit owner: NowOwner): A = value def apply()(implicit owner: LiveOwner): A = value + def now()(implicit owner: NowOwner): A = value + def nowIfSubscribedOption(): Option[A] = someValue } private final class RxLaterWrap[A](state: Rx[A]) extends RxLater[A] { val observable: Observable[A] = state.observable - def nowOption() = Some(state.nowOption()) - def now()(implicit owner: NowOwner) = Some(state.now()) def apply()(implicit owner: LiveOwner) = Some(state()) + def now()(implicit owner: NowOwner) = Some(state.now()) + def nowIfSubscribedOption() = Some(state.nowIfSubscribedOption()) } private final class RxLaterObservable[A](inner: Observable[A]) extends RxLater[A] { @@ -400,9 +398,9 @@ private final class RxLaterObservable[A](inner: Observable[A]) extends RxLater[A val observable: Observable[A] = state.observable.flattenOption - def nowOption() = state.nowOption() - def now()(implicit owner: NowOwner) = state.now() def apply()(implicit owner: LiveOwner) = state() + def now()(implicit owner: NowOwner) = state.now() + def nowIfSubscribedOption() = state.nowIfSubscribedOption() } private final class RxSyncObservable[A](inner: Observable[A]) extends Rx[A] { @@ -411,9 +409,9 @@ private final class RxSyncObservable[A](inner: Observable[A]) extends Rx[A] { val observable: Observable[A] = inner.dropUntilSyncLatest.distinctOnEquals.tapCancel(state.unsafeResetState).multicast(state).refCount - def nowOption() = state.now() - def now()(implicit owner: NowOwner) = owner.unsafeNow(this) def apply()(implicit owner: LiveOwner): A = owner.unsafeLive(this) + def now()(implicit owner: NowOwner) = owner.unsafeNow(this) + def nowIfSubscribedOption() = state.now() } private final class RxWriterObserver[A](val observer: Observer[A]) extends RxWriter[A] @@ -435,7 +433,7 @@ private final class VarLaterSubject[A] extends val observable: Observable[A] = state.observable.flattenOption val observer: Observer[A] = state.observer.contramap(Some.apply) - def nowOption() = state.nowOption() + def nowIfSubscribedOption() = state.nowIfSubscribedOption() def now()(implicit owner: NowOwner) = state.now() def apply()(implicit owner: LiveOwner) = state() } @@ -444,7 +442,7 @@ private final class VarLaterCombine[A](innerRead: RxLater[A], innerWrite: RxWrit val observable = innerRead.observable val observer = innerWrite.observer - def nowOption() = innerRead.nowOption() + def nowIfSubscribedOption() = innerRead.nowIfSubscribedOption() def now()(implicit owner: NowOwner) = innerRead.now() def apply()(implicit owner: LiveOwner) = innerRead.apply() } @@ -455,7 +453,7 @@ private final class VarSubject[A](seed: A) ext val observable: Observable[A] = state.distinctOnEquals val observer: Observer[A] = state - def nowOption() = Some(state.now()) + def nowIfSubscribedOption() = Some(state.now()) def now()(implicit owner: NowOwner) = state.now() def apply()(implicit owner: LiveOwner): A = owner.unsafeLive(this) } @@ -464,7 +462,7 @@ private final class VarCombine[A](innerRead: Rx[A], innerWrite: RxWriter[A]) ext val observable = innerRead.observable val observer = innerWrite.observer - def nowOption() = innerRead.nowOption() + def nowIfSubscribedOption() = innerRead.nowIfSubscribedOption() def now()(implicit owner: NowOwner) = innerRead.now() def apply()(implicit owner: LiveOwner): A = innerRead() } diff --git a/reactive/src/test/scala/colibri/ReactiveSpec.scala b/reactive/src/test/scala/colibri/ReactiveSpec.scala index fb09a033..28d3b54f 100644 --- a/reactive/src/test/scala/colibri/ReactiveSpec.scala +++ b/reactive/src/test/scala/colibri/ReactiveSpec.scala @@ -815,21 +815,21 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { val variable = Var(1) val mapped = variable.map(_ + 1) - variable.nowOption() shouldBe Some(1) + variable.nowIfSubscribedOption() shouldBe Some(1) variable.now() shouldBe 1 - variable.nowOption() shouldBe Some(1) - mapped.nowOption() shouldBe None + variable.nowIfSubscribedOption() shouldBe Some(1) + mapped.nowIfSubscribedOption() shouldBe None mapped.now() shouldBe 2 - mapped.nowOption() shouldBe None + mapped.nowIfSubscribedOption() shouldBe None variable.set(2) - variable.nowOption() shouldBe Some(2) + variable.nowIfSubscribedOption() shouldBe Some(2) variable.now() shouldBe 2 - variable.nowOption() shouldBe Some(2) - mapped.nowOption() shouldBe None + variable.nowIfSubscribedOption() shouldBe Some(2) + mapped.nowIfSubscribedOption() shouldBe None mapped.now() shouldBe 3 - mapped.nowOption() shouldBe None + mapped.nowIfSubscribedOption() shouldBe None } it should "drop" in { @@ -838,25 +838,25 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { val variable1Logged = variable1.drop(1).tap(triggers1 ::= _) triggers1 shouldBe List.empty - variable1Logged.nowOption() shouldBe None + variable1Logged.nowIfSubscribedOption() shouldBe None triggers1 shouldBe List.empty val cancelable = variable1Logged.unsafeSubscribe() triggers1 shouldBe List.empty - variable1Logged.nowOption() shouldBe Some(None) + variable1Logged.nowIfSubscribedOption() shouldBe Some(None) triggers1 shouldBe List.empty variable1.set(2) triggers1 shouldBe List(2) - variable1Logged.nowOption() shouldBe Some(Some(2)) + variable1Logged.nowIfSubscribedOption() shouldBe Some(Some(2)) triggers1 shouldBe List(2) variable1.set(3) triggers1 shouldBe List(3, 2) - variable1Logged.nowOption() shouldBe Some(Some(3)) + variable1Logged.nowIfSubscribedOption() shouldBe Some(Some(3)) triggers1 shouldBe List(3, 2) cancelable.unsafeCancel() @@ -864,7 +864,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { variable1.set(4) triggers1 shouldBe List(3, 2) - variable1Logged.nowOption() shouldBe None + variable1Logged.nowIfSubscribedOption() shouldBe None triggers1 shouldBe List(3, 2) variable1Logged.now() shouldBe None @@ -873,7 +873,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { variable1.set(5) triggers1 shouldBe List(3, 2) - variable1Logged.nowOption() shouldBe None + variable1Logged.nowIfSubscribedOption() shouldBe None triggers1 shouldBe List(3, 2) variable1Logged.now() shouldBe None @@ -897,24 +897,24 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { triggers1 shouldBe List(1) triggerRxCount shouldBe 1 - mapped.nowOption() shouldBe Some(2) + mapped.nowIfSubscribedOption() shouldBe Some(2) mapped.now() shouldBe 2 - mapped.nowOption() shouldBe Some(2) + mapped.nowIfSubscribedOption() shouldBe Some(2) variable1.set(2) triggers1 shouldBe List(2, 1) triggerRxCount shouldBe 2 - mapped.nowOption() shouldBe Some(3) + mapped.nowIfSubscribedOption() shouldBe Some(3) mapped.now() shouldBe 3 - mapped.nowOption() shouldBe Some(3) + mapped.nowIfSubscribedOption() shouldBe Some(3) cancelable.unsafeCancel() - mapped.nowOption() shouldBe None + mapped.nowIfSubscribedOption() shouldBe None mapped.now() shouldBe 3 - mapped.nowOption() shouldBe None + mapped.nowIfSubscribedOption() shouldBe None triggers1 shouldBe List(2, 2, 1) triggerRxCount shouldBe 3 @@ -944,17 +944,17 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { triggers2 shouldBe List.empty triggerRxCount shouldBe 0 - mapped.nowOption() shouldBe None + mapped.nowIfSubscribedOption() shouldBe None mapped.now() shouldBe 2 - mapped.nowOption() shouldBe None + mapped.nowIfSubscribedOption() shouldBe None triggers1 shouldBe List(1) triggers2 shouldBe List(1) triggerRxCount shouldBe 1 - mapped.nowOption() shouldBe None + mapped.nowIfSubscribedOption() shouldBe None mapped.now() shouldBe 2 - mapped.nowOption() shouldBe None + mapped.nowIfSubscribedOption() shouldBe None triggers1 shouldBe List(1, 1) triggers2 shouldBe List(1, 1) @@ -966,9 +966,9 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { triggers2 shouldBe List(1, 1, 1) triggerRxCount shouldBe 3 - mapped.nowOption() shouldBe Some(2) + mapped.nowIfSubscribedOption() shouldBe Some(2) mapped.now() shouldBe 2 - mapped.nowOption() shouldBe Some(2) + mapped.nowIfSubscribedOption() shouldBe Some(2) variable1.set(2) @@ -976,9 +976,9 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { triggers2 shouldBe List(1, 1, 1) triggerRxCount shouldBe 4 - mapped.nowOption() shouldBe Some(3) + mapped.nowIfSubscribedOption() shouldBe Some(3) mapped.now() shouldBe 3 - mapped.nowOption() shouldBe Some(3) + mapped.nowIfSubscribedOption() shouldBe Some(3) triggers1 shouldBe List(2, 1, 1, 1) triggers2 shouldBe List(1, 1, 1) @@ -986,9 +986,9 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { variable2.set(10) - mapped.nowOption() shouldBe Some(3) + mapped.nowIfSubscribedOption() shouldBe Some(3) mapped.now() shouldBe 3 - mapped.nowOption() shouldBe Some(3) + mapped.nowIfSubscribedOption() shouldBe Some(3) triggers1 shouldBe List(2, 1, 1, 1) triggers2 shouldBe List(10, 1, 1, 1) @@ -996,9 +996,9 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { variable1.set(3) - mapped.nowOption() shouldBe Some(13) + mapped.nowIfSubscribedOption() shouldBe Some(13) mapped.now() shouldBe 13 - mapped.nowOption() shouldBe Some(13) + mapped.nowIfSubscribedOption() shouldBe Some(13) triggers1 shouldBe List(3, 2, 1, 1, 1) triggers2 shouldBe List(10, 1, 1, 1) @@ -1006,9 +1006,9 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { cancelable.unsafeCancel() - mapped.nowOption() shouldBe None + mapped.nowIfSubscribedOption() shouldBe None mapped.now() shouldBe 13 - mapped.nowOption() shouldBe None + mapped.nowIfSubscribedOption() shouldBe None triggers1 shouldBe List(3, 3, 2, 1, 1, 1) triggers2 shouldBe List(10, 10, 1, 1, 1) From 2fb358cd906d6d9c4c52e8376ee44320da2fa4aa Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 16 Jan 2023 13:37:36 +0100 Subject: [PATCH 18/44] format --- .../scala/colibri/reactive/Reactive.scala | 93 ++++++++++--------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index beebc558..81d08d9e 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -11,7 +11,9 @@ import scala.reflect.ClassTag import scala.util.control.NonFatal object RxMissingNowException - extends Exception("Missing current value inside an RxState (Rx or RxLater). Make sure, the RxState has active subscriptions when calling nowIfSubscribed.") + extends Exception( + "Missing current value inside an RxState (Rx or RxLater). Make sure, the RxState has active subscriptions when calling nowIfSubscribed.", + ) trait RxSource[+A] { type Self[+X] <: RxSource[X] @@ -27,7 +29,7 @@ trait RxSource[+A] { final def unsafeSubscribe(): Cancelable = observable.unsafeSubscribe() final def unsafeSubscribe(writer: RxWriter[A]): Cancelable = observable.unsafeSubscribe(writer.observer) - final def unsafeForeach(f: A => Unit): Cancelable = observable.unsafeForeach(f) + final def unsafeForeach(f: A => Unit): Cancelable = observable.unsafeForeach(f) final def hot: SyncIO[SelfSync[A]] = SyncIO(unsafeHot()) final def unsafeHot(): SelfSync[A] = { @@ -35,23 +37,23 @@ trait RxSource[+A] { selfRxSourceSync } - final def map[B](f: A => B): SelfSync[B] = transformRxSourceSync(_.map(f)) - final def tap(f: A => Unit): SelfSync[A] = transformRxSourceSync(_.tap(f)) + final def map[B](f: A => B): SelfSync[B] = transformRxSourceSync(_.map(f)) + final def tap(f: A => Unit): SelfSync[A] = transformRxSourceSync(_.tap(f)) final def collect[B](f: PartialFunction[A, B]): Self[B] = transformRxSource(_.collect(f)) final def mapEither[B](f: A => Either[Throwable, B]): Self[B] = transformRxSource(_.mapEither(f)) - final def drop(n: Int): Self[A] = transformRxSource(_.drop(n)) + final def drop(n: Int): Self[A] = transformRxSource(_.drop(n)) final def mapSyncEffect[F[_]: RunSyncEffect, B](f: A => F[B]): SelfSync[B] = transformRxSourceSync(_.mapEffect(f)) - final def mapEffect[F[_]: RunEffect, B](f: A => F[B]): Self[B] = transformRxSource(_.mapEffect(f)) - final def mapFuture[B](f: A => Future[B]): Self[B] = transformRxSource(_.mapFuture(f)) + final def mapEffect[F[_]: RunEffect, B](f: A => F[B]): Self[B] = transformRxSource(_.mapEffect(f)) + final def mapFuture[B](f: A => Future[B]): Self[B] = transformRxSource(_.mapFuture(f)) final def as[B](value: B): SelfSync[B] = transformRxSourceSync(_.as(value)) final def asEval[B](value: => B): SelfSync[B] = transformRxSourceSync(_.asEval(value)) final def asSyncEffect[F[_]: RunSyncEffect, B](value: F[B]): SelfSync[B] = transformRxSourceSync(_.asEffect(value)) - final def asEffect[F[_]: RunEffect, B](value: F[B]): Self[B] = transformRxSource(_.asEffect(value)) - final def asFuture[B](value: => Future[B]): Self[B] = transformRxSource(_.asFuture(value)) + final def asEffect[F[_]: RunEffect, B](value: F[B]): Self[B] = transformRxSource(_.asEffect(value)) + final def asFuture[B](value: => Future[B]): Self[B] = transformRxSource(_.asFuture(value)) final def via(writer: RxWriter[A]): SelfSync[A] = transformRxSourceSync(_.via(writer.observer)) @@ -94,30 +96,30 @@ object RxSource { } trait RxEvent[+A] extends RxSource[A] { - type Self[+X] = RxEvent[X] + type Self[+X] = RxEvent[X] type SelfSync[+X] = RxEvent[X] - final override def selfRxSourceSync: RxEvent[A] = this - final override def transformRxSource[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) - final override def transformRxSourceSync[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) + final override def selfRxSourceSync: RxEvent[A] = this + final override def transformRxSource[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) + final override def transformRxSourceSync[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) } object RxEvent extends RxPlatform { private val _empty: RxEvent[Nothing] = observableUnshared(Observable.empty) - def empty[A]: RxEvent[A] = _empty + def empty[A]: RxEvent[A] = _empty def apply[A](values: A*): RxEvent[A] = iterable(values) def iterable[A](values: Iterable[A]): RxEvent[A] = observableUnshared(Observable.fromIterable(values)) - def future[A](future: => Future[A]): RxEvent[A] = observable(Observable.fromFuture(future)) + def future[A](future: => Future[A]): RxEvent[A] = observable(Observable.fromFuture(future)) def effect[F[_]: RunEffect, A](effect: F[A]): RxEvent[A] = observable(Observable.fromEffect(effect)) def merge[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.mergeIterable(rxs.map(_.observable))) def switch[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.switchIterable(rxs.map(_.observable))) def concat[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.concatIterable(rxs.map(_.observable))) - def observable[A](observable: Observable[A]): RxEvent[A] = observableUnshared(observable.publish.refCount) + def observable[A](observable: Observable[A]): RxEvent[A] = observableUnshared(observable.publish.refCount) private def observableUnshared[A](observable: Observable[A]): RxEvent[A] = new RxEventObservable(observable) } @@ -137,20 +139,20 @@ trait RxLater[+A] extends RxState[A] { final def nowIfSubscribedOpt(): Option[A] = nowIfSubscribedOption().getOrElse(throw RxMissingNowException) - final override def selfRxSourceSync: RxLater[A] = this + final override def selfRxSourceSync: RxLater[A] = this final override def transformRxSourceSync[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) } object RxLater { def empty[A]: RxLater[A] = RxLaterEmpty - def future[A](future: => Future[A]): RxLater[A] = observable(Observable.fromFuture(future)) + def future[A](future: => Future[A]): RxLater[A] = observable(Observable.fromFuture(future)) def effect[F[_]: RunEffect, A](effect: F[A]): RxLater[A] = observable(Observable.fromEffect(effect)) def observable[A](observable: Observable[A]): RxLater[A] = new RxLaterObservable(observable) @inline implicit final class RxLaterOps[A](private val self: RxLater[A]) extends AnyVal { - def toRx: Rx[Option[A]] = Rx.observableSeed(self.observable.map[Option[A]](Some.apply))(None) + def toRx: Rx[Option[A]] = Rx.observableSeed(self.observable.map[Option[A]](Some.apply))(None) def toRx(seed: => A): Rx[A] = Rx.observableSeed(self.observable)(seed) } } @@ -164,7 +166,7 @@ trait Rx[+A] extends RxState[A] { final def nowIfSubscribed(): A = nowIfSubscribedOption().getOrElse(throw RxMissingNowException) - final override def selfRxSourceSync: Rx[A] = this + final override def selfRxSourceSync: Rx[A] = this final override def transformRxSourceSync[B](f: Observable[A] => Observable[B]): Rx[B] = Rx.observableSync(f(observable)) } @@ -243,7 +245,7 @@ trait VarEvent[A] extends RxWriter[A] with RxEvent[A] { } object VarEvent { - def apply[A](): VarEvent[A] = new VarEventSubject + def apply[A](): VarEvent[A] = new VarEventSubject def subject[A](read: Subject[A]): VarEvent[A] = combine(RxEvent.observable(read), RxWriter.observer(read)) @@ -253,13 +255,14 @@ object VarEvent { trait VarState[A] extends RxWriter[A] with RxState[A] trait VarLater[A] extends VarState[A] with RxLater[A] { - final def transformVarLater[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = VarLater.combine(g(this), f(this)) + final def transformVarLater[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = + VarLater.combine(g(this), f(this)) final def transformVarLaterRx(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.combine(g(this), this) - final def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.combine(this, f(this)) + final def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.combine(this, f(this)) } object VarLater { - def apply[A](): VarLater[A] = new VarLaterSubject + def apply[A](): VarLater[A] = new VarLaterSubject def subject[A](read: Subject[A]): VarLater[A] = combine(RxLater.observable(read), RxWriter.observer(read)) @@ -368,29 +371,29 @@ private final class RxEventObservable[A](val observable: Observable[A]) extends private final object RxLaterEmpty extends RxLater[Nothing] { private lazy val someNone = Some(None) - val observable = Observable.empty + val observable = Observable.empty def apply()(implicit owner: LiveOwner) = None def now()(implicit owner: NowOwner) = None - def nowIfSubscribedOption() = someNone + def nowIfSubscribedOption() = someNone } private final class RxConst[A](value: A) extends Rx[A] { private lazy val someValue = Some(value) - val observable: Observable[A] = Observable.pure(value) + val observable: Observable[A] = Observable.pure(value) def apply()(implicit owner: LiveOwner): A = value def now()(implicit owner: NowOwner): A = value - def nowIfSubscribedOption(): Option[A] = someValue + def nowIfSubscribedOption(): Option[A] = someValue } private final class RxLaterWrap[A](state: Rx[A]) extends RxLater[A] { val observable: Observable[A] = state.observable def apply()(implicit owner: LiveOwner) = Some(state()) - def now()(implicit owner: NowOwner) = Some(state.now()) - def nowIfSubscribedOption() = Some(state.nowIfSubscribedOption()) + def now()(implicit owner: NowOwner) = Some(state.now()) + def nowIfSubscribedOption() = Some(state.nowIfSubscribedOption()) } private final class RxLaterObservable[A](inner: Observable[A]) extends RxLater[A] { @@ -399,8 +402,8 @@ private final class RxLaterObservable[A](inner: Observable[A]) extends RxLater[A val observable: Observable[A] = state.observable.flattenOption def apply()(implicit owner: LiveOwner) = state() - def now()(implicit owner: NowOwner) = state.now() - def nowIfSubscribedOption() = state.nowIfSubscribedOption() + def now()(implicit owner: NowOwner) = state.now() + def nowIfSubscribedOption() = state.nowIfSubscribedOption() } private final class RxSyncObservable[A](inner: Observable[A]) extends Rx[A] { @@ -411,7 +414,7 @@ private final class RxSyncObservable[A](inner: Observable[A]) extends Rx[A] { def apply()(implicit owner: LiveOwner): A = owner.unsafeLive(this) def now()(implicit owner: NowOwner) = owner.unsafeNow(this) - def nowIfSubscribedOption() = state.now() + def nowIfSubscribedOption() = state.now() } private final class RxWriterObserver[A](val observer: Observer[A]) extends RxWriter[A] @@ -427,42 +430,42 @@ private final class VarEventCombine[A](innerRead: RxEvent[A], innerWrite: RxWrit val observer = innerWrite.observer } -private final class VarLaterSubject[A] extends VarLater[A] { +private final class VarLaterSubject[A] extends VarLater[A] { private val state = Var[Option[A]](None) val observable: Observable[A] = state.observable.flattenOption val observer: Observer[A] = state.observer.contramap(Some.apply) - def nowIfSubscribedOption() = state.nowIfSubscribedOption() - def now()(implicit owner: NowOwner) = state.now() + def nowIfSubscribedOption() = state.nowIfSubscribedOption() + def now()(implicit owner: NowOwner) = state.now() def apply()(implicit owner: LiveOwner) = state() } private final class VarLaterCombine[A](innerRead: RxLater[A], innerWrite: RxWriter[A]) extends VarLater[A] { - val observable = innerRead.observable - val observer = innerWrite.observer + val observable = innerRead.observable + val observer = innerWrite.observer - def nowIfSubscribedOption() = innerRead.nowIfSubscribedOption() - def now()(implicit owner: NowOwner) = innerRead.now() + def nowIfSubscribedOption() = innerRead.nowIfSubscribedOption() + def now()(implicit owner: NowOwner) = innerRead.now() def apply()(implicit owner: LiveOwner) = innerRead.apply() } -private final class VarSubject[A](seed: A) extends Var[A] { +private final class VarSubject[A](seed: A) extends Var[A] { private val state = new BehaviorSubject[A](seed) val observable: Observable[A] = state.distinctOnEquals val observer: Observer[A] = state - def nowIfSubscribedOption() = Some(state.now()) + def nowIfSubscribedOption() = Some(state.now()) def now()(implicit owner: NowOwner) = state.now() def apply()(implicit owner: LiveOwner): A = owner.unsafeLive(this) } private final class VarCombine[A](innerRead: Rx[A], innerWrite: RxWriter[A]) extends Var[A] { - val observable = innerRead.observable - val observer = innerWrite.observer + val observable = innerRead.observable + val observer = innerWrite.observer - def nowIfSubscribedOption() = innerRead.nowIfSubscribedOption() + def nowIfSubscribedOption() = innerRead.nowIfSubscribedOption() def now()(implicit owner: NowOwner) = innerRead.now() def apply()(implicit owner: LiveOwner): A = innerRead() } From 258726139be798fbd966c3da257a37900ed222b0 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 16 Jan 2023 13:41:44 +0100 Subject: [PATCH 19/44] fix --- reactive/src/main/scala/colibri/reactive/Reactive.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 81d08d9e..ea6eea38 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -368,7 +368,7 @@ object Var { private final class RxEventObservable[A](val observable: Observable[A]) extends RxEvent[A] -private final object RxLaterEmpty extends RxLater[Nothing] { +private object RxLaterEmpty extends RxLater[Nothing] { private lazy val someNone = Some(None) val observable = Observable.empty From 24eabdde6fd12c910c6d677c209f852098b6f004 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 16 Jan 2023 14:42:59 +0100 Subject: [PATCH 20/44] remove unnecessary type params for observer flatten methods --- colibri/src/main/scala/colibri/Observer.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colibri/src/main/scala/colibri/Observer.scala b/colibri/src/main/scala/colibri/Observer.scala index 5c1c5989..daa6ca40 100644 --- a/colibri/src/main/scala/colibri/Observer.scala +++ b/colibri/src/main/scala/colibri/Observer.scala @@ -154,9 +154,9 @@ object Observer { def unsafeOnError(error: Throwable): Unit = sink.unsafeOnError(error) } - def contraflattenIterable[B]: Observer[Iterable[A]] = contramapIterable(identity) - def contraflattenEither[B]: Observer[Either[Throwable, A]] = contramapEither(identity) - def contraflattenOption[B]: Observer[Option[A]] = contramapFilter(identity) + def contraflattenIterable: Observer[Iterable[A]] = contramapIterable(identity) + def contraflattenEither: Observer[Either[Throwable, A]] = contramapEither(identity) + def contraflattenOption: Observer[Option[A]] = contramapFilter(identity) // TODO return effect def contrascan[B](seed: A)(f: (A, B) => A): Observer[B] = new Observer[B] { From ef6fa70ed7e4e55a40d24e6cc2d95e566b5c408a Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 16 Jan 2023 14:47:25 +0100 Subject: [PATCH 21/44] fix stateful transform with Var.from (renamed from combine) --- .../scala/colibri/reactive/Reactive.scala | 114 +++++++++++------- .../src/test/scala/colibri/ReactiveSpec.scala | 29 +++++ 2 files changed, 102 insertions(+), 41 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index ea6eea38..75b240bf 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -238,44 +238,45 @@ object RxWriter { } trait VarEvent[A] extends RxWriter[A] with RxEvent[A] { - final def transformVarEvent[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = - VarEvent.combine(g(this), f(this)) - final def transformVarEventRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.combine(g(this), this) - final def transformVarEventRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.combine(this, f(this)) + final def transformVarEvent[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = VarEvent.from(g(this), f(this)) + final def transformVarEventRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.from(g(this), this) + final def transformVarEventRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.fromStateless(this, f(this)) } object VarEvent { def apply[A](): VarEvent[A] = new VarEventSubject - def subject[A](read: Subject[A]): VarEvent[A] = combine(RxEvent.observable(read), RxWriter.observer(read)) + def subject[A](read: Subject[A]): VarEvent[A] = fromStateless(RxEvent.observable(read), RxWriter.observer(read)) - def combine[A](read: RxEvent[A], write: RxWriter[A]): VarEvent[A] = new VarEventCombine(read, write) + def from[A](read: RxEvent[A], write: RxWriter[A]): VarEvent[A] = new VarEventFromStateful(read, write) + def fromStateless[A](read: RxEvent[A], write: RxWriter[A]): VarEvent[A] = new VarEventFromStateless(read, write) } trait VarState[A] extends RxWriter[A] with RxState[A] trait VarLater[A] extends VarState[A] with RxLater[A] { final def transformVarLater[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = - VarLater.combine(g(this), f(this)) - final def transformVarLaterRx(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.combine(g(this), this) - final def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.combine(this, f(this)) + VarLater.from(g(this), f(this)) + final def transformVarLaterRx(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.from(g(this), this) + final def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.from(this, f(this)) } object VarLater { def apply[A](): VarLater[A] = new VarLaterSubject - def subject[A](read: Subject[A]): VarLater[A] = combine(RxLater.observable(read), RxWriter.observer(read)) + def subject[A](read: Subject[A]): VarLater[A] = fromStateless(RxLater.observable(read), RxWriter.observer(read)) - def combine[A](read: RxLater[A], write: RxWriter[A]): VarLater[A] = new VarLaterCombine(read, write) + def from[A](read: RxLater[A], write: RxWriter[A]): VarLater[A] = new VarLaterFromStateful(read, write) + def fromStateless[A](read: RxLater[A], write: RxWriter[A]): VarLater[A] = new VarLaterFromStateless(read, write) } trait Var[A] extends VarState[A] with Rx[A] { final def updateIfSubscribed(f: A => A): Unit = set(f(nowIfSubscribed())) final def update(f: A => A): Unit = set(f(now())) - final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: Rx[A] => Rx[A2]): Var[A2] = Var.combine(g(this), f(this)) - final def transformVarRx(g: Rx[A] => Rx[A]): Var[A] = Var.combine(g(this), this) - final def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.combine(this, f(this)) + final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: Rx[A] => Rx[A2]): Var[A2] = Var.from(g(this), f(this)) + final def transformVarRx(g: Rx[A] => Rx[A]): Var[A] = Var.from(g(this), this) + final def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.fromStateless(this, f(this)) def imap[A2](f: A2 => A)(g: A => A2): Var[A2] = transformVar(_.contramap(f))(_.map(g)) def lens[B](read: A => B)(write: (A, B) => A): Var[B] = @@ -298,9 +299,10 @@ trait Var[A] extends VarState[A] with Rx[A] { object Var { def apply[A](seed: A): Var[A] = new VarSubject(seed) - def subjectSync[A](read: Subject[A]): Var[A] = combine(Rx.observableSync(read), RxWriter.observer(read)) + def subjectSync[A](read: Subject[A]): Var[A] = fromStateless(Rx.observableSync(read), RxWriter.observer(read)) - def combine[A](read: Rx[A], write: RxWriter[A]): Var[A] = new VarCombine(read, write) + def from[A](read: Rx[A], write: RxWriter[A]): Var[A] = new VarFromStateful(read, write) + def fromStateless[A](read: Rx[A], write: RxWriter[A]): Var[A] = new VarFromStateless(read, write) @inline implicit class SeqVarOperations[A](rxvar: Var[Seq[A]]) { def sequence: Rx[Seq[Var[A]]] = Rx.observableSync(new Observable[Seq[Var[A]]] { @@ -319,7 +321,7 @@ object Var { sink.unsafeOnError(error) } } - Var.combine(Rx.const(a), RxWriter.observer(observer)) + Var.fromStateless(Rx.const(a), RxWriter.observer(observer)) }) }, sink.unsafeOnError, @@ -371,7 +373,7 @@ private final class RxEventObservable[A](val observable: Observable[A]) extends private object RxLaterEmpty extends RxLater[Nothing] { private lazy val someNone = Some(None) - val observable = Observable.empty + def observable = Observable.empty def apply()(implicit owner: LiveOwner) = None def now()(implicit owner: NowOwner) = None @@ -389,7 +391,7 @@ private final class RxConst[A](value: A) extends Rx[A] { } private final class RxLaterWrap[A](state: Rx[A]) extends RxLater[A] { - val observable: Observable[A] = state.observable + def observable: Observable[A] = state.observable def apply()(implicit owner: LiveOwner) = Some(state()) def now()(implicit owner: NowOwner) = Some(state.now()) @@ -407,12 +409,12 @@ private final class RxLaterObservable[A](inner: Observable[A]) extends RxLater[A } private final class RxSyncObservable[A](inner: Observable[A]) extends Rx[A] { - private val state = new ReplayLatestSubject[A]() + private val state = Subject.replayLatest[A]() val observable: Observable[A] = inner.dropUntilSyncLatest.distinctOnEquals.tapCancel(state.unsafeResetState).multicast(state).refCount - def apply()(implicit owner: LiveOwner): A = owner.unsafeLive(this) + def apply()(implicit owner: LiveOwner) = owner.unsafeLive(this) def now()(implicit owner: NowOwner) = owner.unsafeNow(this) def nowIfSubscribedOption() = state.now() } @@ -420,14 +422,22 @@ private final class RxSyncObservable[A](inner: Observable[A]) extends Rx[A] { private final class RxWriterObserver[A](val observer: Observer[A]) extends RxWriter[A] private final class VarEventSubject[A] extends VarEvent[A] { - private val state = new PublishSubject[A] - val observable: Observable[A] = state - val observer: Observer[A] = state + private val state = Subject.publish[A]() + + def observable: Observable[A] = state + def observer: Observer[A] = state } -private final class VarEventCombine[A](innerRead: RxEvent[A], innerWrite: RxWriter[A]) extends VarEvent[A] { - val observable = innerRead.observable - val observer = innerWrite.observer +private final class VarEventFromStateless[A](innerRead: RxEvent[A], innerWrite: RxWriter[A]) extends VarEvent[A] { + def observable = innerRead.observable + def observer = innerWrite.observer +} + +private final class VarEventFromStateful[A](innerRead: RxEvent[A], innerWrite: RxWriter[A]) extends VarEvent[A] { + private val state = Subject.publish[A]() + + val observable = innerRead.observable.subscribing(state.via(innerWrite.observer)).multicast(state).refCount + def observer = state } private final class VarLaterSubject[A] extends VarLater[A] { @@ -436,36 +446,58 @@ private final class VarLaterSubject[A] extends VarLater[A] { val observable: Observable[A] = state.observable.flattenOption val observer: Observer[A] = state.observer.contramap(Some.apply) - def nowIfSubscribedOption() = state.nowIfSubscribedOption() - def now()(implicit owner: NowOwner) = state.now() def apply()(implicit owner: LiveOwner) = state() + def now()(implicit owner: NowOwner) = state.now() + def nowIfSubscribedOption() = state.nowIfSubscribedOption() } -private final class VarLaterCombine[A](innerRead: RxLater[A], innerWrite: RxWriter[A]) extends VarLater[A] { - val observable = innerRead.observable - val observer = innerWrite.observer +private final class VarLaterFromStateless[A](innerRead: RxLater[A], innerWrite: RxWriter[A]) extends VarLater[A] { + def observable = innerRead.observable + def observer = innerWrite.observer - def nowIfSubscribedOption() = innerRead.nowIfSubscribedOption() - def now()(implicit owner: NowOwner) = innerRead.now() def apply()(implicit owner: LiveOwner) = innerRead.apply() + def now()(implicit owner: NowOwner) = innerRead.now() + def nowIfSubscribedOption() = innerRead.nowIfSubscribedOption() +} + +private final class VarLaterFromStateful[A](innerRead: RxLater[A], innerWrite: RxWriter[A]) extends VarLater[A] { + private val state: Var[Option[A]] = Var.from[Option[A]](innerRead.toRx, innerWrite.transformRxWriter(_.contraflattenOption)) + + val observable = state.observable.flattenOption + def observer = state.observer.contramap(Some.apply) + + def apply()(implicit owner: LiveOwner) = state() + def now()(implicit owner: NowOwner) = state.now() + def nowIfSubscribedOption() = state.nowIfSubscribedOption() } private final class VarSubject[A](seed: A) extends Var[A] { - private val state = new BehaviorSubject[A](seed) + private val state = Subject.behavior[A](seed) val observable: Observable[A] = state.distinctOnEquals - val observer: Observer[A] = state + def observer: Observer[A] = state - def nowIfSubscribedOption() = Some(state.now()) + def apply()(implicit owner: LiveOwner) = owner.unsafeLive(this) def now()(implicit owner: NowOwner) = state.now() - def apply()(implicit owner: LiveOwner): A = owner.unsafeLive(this) + def nowIfSubscribedOption() = Some(state.now()) } -private final class VarCombine[A](innerRead: Rx[A], innerWrite: RxWriter[A]) extends Var[A] { +private final class VarFromStateless[A](innerRead: Rx[A], innerWrite: RxWriter[A]) extends Var[A] { val observable = innerRead.observable val observer = innerWrite.observer - def nowIfSubscribedOption() = innerRead.nowIfSubscribedOption() + def apply()(implicit owner: LiveOwner) = innerRead() def now()(implicit owner: NowOwner) = innerRead.now() - def apply()(implicit owner: LiveOwner): A = innerRead() + def nowIfSubscribedOption() = innerRead.nowIfSubscribedOption() +} + +private final class VarFromStateful[A](innerRead: Rx[A], innerWrite: RxWriter[A]) extends Var[A] { + private val state = Subject.replayLatest[A]() + + val observable = innerRead.observable.subscribing(state.via(innerWrite.observer)).multicast(state).refCount + def observer = state + + def apply()(implicit owner: LiveOwner) = owner.unsafeLive(this) + def now()(implicit owner: NowOwner) = owner.unsafeNow(this) + def nowIfSubscribedOption() = state.now() } diff --git a/reactive/src/test/scala/colibri/ReactiveSpec.scala b/reactive/src/test/scala/colibri/ReactiveSpec.scala index 28d3b54f..1266fb16 100644 --- a/reactive/src/test/scala/colibri/ReactiveSpec.scala +++ b/reactive/src/test/scala/colibri/ReactiveSpec.scala @@ -710,6 +710,35 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { } } + it should "transform and keep state" in { + val original: Var[Int] = Var(1) + val encoded: Var[String] = original.transformVar[String]( + _.contramapIterable(str => str.toIntOption) + )( + _.map(num => num.toString) + ) + + encoded.unsafeSubscribe() + + original.nowIfSubscribed() shouldBe 1 + encoded.nowIfSubscribed() shouldBe "1" + + original.set(2) + + original.nowIfSubscribed() shouldBe 2 + encoded.nowIfSubscribed() shouldBe "2" + + encoded.set("3") + + original.nowIfSubscribed() shouldBe 3 + encoded.nowIfSubscribed() shouldBe "3" + + encoded.set("nope") + + original.nowIfSubscribed() shouldBe 3 + encoded.nowIfSubscribed() shouldBe "nope" + } + it should "lens" in { val a: Var[(Int, String)] = Var((0, "Wurst")) val b: Var[String] = a.lens(_._2)((a, b) => a.copy(_2 = b)) From a1972efe56dc21afb2db2d0c1d8ed3c1e21b99ae Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 16 Jan 2023 17:20:17 +0100 Subject: [PATCH 22/44] simplify --- .../scala/colibri/reactive/Reactive.scala | 49 +---- .../src/test/scala/colibri/ReactiveSpec.scala | 173 +++++++++++++++++- 2 files changed, 175 insertions(+), 47 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 75b240bf..b3fe4e94 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -133,12 +133,6 @@ trait RxState[+A] extends RxSource[A] { trait RxLater[+A] extends RxState[A] { type SelfSync[+X] = RxLater[X] - def apply()(implicit owner: LiveOwner): Option[A] - def now()(implicit owner: NowOwner): Option[A] - def nowIfSubscribedOption(): Option[Option[A]] - - final def nowIfSubscribedOpt(): Option[A] = nowIfSubscribedOption().getOrElse(throw RxMissingNowException) - final override def selfRxSourceSync: RxLater[A] = this final override def transformRxSourceSync[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) } @@ -371,13 +365,7 @@ object Var { private final class RxEventObservable[A](val observable: Observable[A]) extends RxEvent[A] private object RxLaterEmpty extends RxLater[Nothing] { - private lazy val someNone = Some(None) - def observable = Observable.empty - - def apply()(implicit owner: LiveOwner) = None - def now()(implicit owner: NowOwner) = None - def nowIfSubscribedOption() = someNone } private final class RxConst[A](value: A) extends Rx[A] { @@ -392,20 +380,13 @@ private final class RxConst[A](value: A) extends Rx[A] { private final class RxLaterWrap[A](state: Rx[A]) extends RxLater[A] { def observable: Observable[A] = state.observable - - def apply()(implicit owner: LiveOwner) = Some(state()) - def now()(implicit owner: NowOwner) = Some(state.now()) - def nowIfSubscribedOption() = Some(state.nowIfSubscribedOption()) } private final class RxLaterObservable[A](inner: Observable[A]) extends RxLater[A] { - private val state = Rx.observableSeed(inner.map[Option[A]](Some.apply))(None) - - val observable: Observable[A] = state.observable.flattenOption + private val state = Subject.replayLatest[A]() - def apply()(implicit owner: LiveOwner) = state() - def now()(implicit owner: NowOwner) = state.now() - def nowIfSubscribedOption() = state.nowIfSubscribedOption() + val observable: Observable[A] = + inner.dropUntilSyncLatest.distinctOnEquals.tapCancel(state.unsafeResetState).multicast(state).refCount } private final class RxSyncObservable[A](inner: Observable[A]) extends Rx[A] { @@ -441,34 +422,22 @@ private final class VarEventFromStateful[A](innerRead: RxEvent[A], innerWrite: R } private final class VarLaterSubject[A] extends VarLater[A] { - private val state = Var[Option[A]](None) - - val observable: Observable[A] = state.observable.flattenOption - val observer: Observer[A] = state.observer.contramap(Some.apply) + private val state = Subject.replayLatest[A]() - def apply()(implicit owner: LiveOwner) = state() - def now()(implicit owner: NowOwner) = state.now() - def nowIfSubscribedOption() = state.nowIfSubscribedOption() + val observable: Observable[A] = state.distinctOnEquals + def observer: Observer[A] = state } private final class VarLaterFromStateless[A](innerRead: RxLater[A], innerWrite: RxWriter[A]) extends VarLater[A] { def observable = innerRead.observable def observer = innerWrite.observer - - def apply()(implicit owner: LiveOwner) = innerRead.apply() - def now()(implicit owner: NowOwner) = innerRead.now() - def nowIfSubscribedOption() = innerRead.nowIfSubscribedOption() } private final class VarLaterFromStateful[A](innerRead: RxLater[A], innerWrite: RxWriter[A]) extends VarLater[A] { - private val state: Var[Option[A]] = Var.from[Option[A]](innerRead.toRx, innerWrite.transformRxWriter(_.contraflattenOption)) - - val observable = state.observable.flattenOption - def observer = state.observer.contramap(Some.apply) + private val state = Subject.replayLatest[A]() - def apply()(implicit owner: LiveOwner) = state() - def now()(implicit owner: NowOwner) = state.now() - def nowIfSubscribedOption() = state.nowIfSubscribedOption() + val observable = innerRead.observable.subscribing(state.via(innerWrite.observer)).multicast(state).refCount + def observer = state } private final class VarSubject[A](seed: A) extends Var[A] { diff --git a/reactive/src/test/scala/colibri/ReactiveSpec.scala b/reactive/src/test/scala/colibri/ReactiveSpec.scala index 1266fb16..96e6e534 100644 --- a/reactive/src/test/scala/colibri/ReactiveSpec.scala +++ b/reactive/src/test/scala/colibri/ReactiveSpec.scala @@ -130,7 +130,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { received2 shouldBe List(8, 7, 6, 5, 4, 3, 2) } - it should "nested owners" in { + it should "nested rx" in { var received1 = List.empty[Int] var innerRx = List.empty[Int] var outerRx = List.empty[Int] @@ -185,7 +185,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { received1 shouldBe List(12, 9, 6, 3, 2) } - it should "nested owners 2" in { + it should "nested rx 2" in { var received1 = List.empty[Int] var innerRx = List.empty[Int] var outerRx = List.empty[Int] @@ -590,38 +590,91 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { liveCounter shouldBe 5 } - it should "collect" in { + it should "collect initial some" in { + var collectedStates = Vector.empty[Int] + val variable = Var[Option[Int]](Some(1)) val collected = variable.collect { case Some(x) => x }.toRx(0) - var collectedStates = Vector.empty[Int] collected.unsafeForeach(collectedStates :+= _) + collected.now() shouldBe 1 collectedStates shouldBe Vector(1) variable.set(None) + collected.now() shouldBe 1 collectedStates shouldBe Vector(1) variable.set(Some(17)) + collected.now() shouldBe 17 collectedStates shouldBe Vector(1, 17) - } it should "collect initial none" in { + var collectedStates = Vector.empty[Int] + val variable = Var[Option[Int]](None) val collected = variable.collect { case Some(x) => x }.toRx(0) - var collectedStates = Vector.empty[Int] collected.unsafeForeach(collectedStates :+= _) + collected.now() shouldBe 0 collectedStates shouldBe Vector(0) variable.set(None) + collected.now() shouldBe 0 collectedStates shouldBe Vector(0) variable.set(Some(17)) + collected.now() shouldBe 17 collectedStates shouldBe Vector(0, 17) + } + + it should "collect later initial none" in { + var tapStates = Vector.empty[Int] + var collectedStates = Vector.empty[Int] + + val variable = Var[Option[Int]](None) + val collected = variable.collect { case Some(x) => x }.tap(tapStates :+= _) + + val cancelable = collected.unsafeForeach(collectedStates :+= _) + collectedStates shouldBe Vector.empty + tapStates shouldBe Vector.empty + + variable.set(Some(0)) + collectedStates shouldBe Vector(0) + tapStates shouldBe Vector(0) + + collected.toRx.now() shouldBe Some(0) + collectedStates shouldBe Vector(0) + tapStates shouldBe Vector(0) + + variable.set(None) + collectedStates shouldBe Vector(0) + tapStates shouldBe Vector(0) + + collected.toRx.now() shouldBe Some(0) + collectedStates shouldBe Vector(0) + tapStates shouldBe Vector(0) + + variable.set(Some(17)) + collectedStates shouldBe Vector(0, 17) + tapStates shouldBe Vector(0, 17) + + collected.toRx.now() shouldBe Some(17) + collectedStates shouldBe Vector(0, 17) + tapStates shouldBe Vector(0, 17) + + cancelable.unsafeCancel() + + variable.set(Some(18)) + collectedStates shouldBe Vector(0, 17) + tapStates shouldBe Vector(0, 17) + + collected.toRx.now() shouldBe Some(18) + collectedStates shouldBe Vector(0, 17) + tapStates shouldBe Vector(0, 17, 18) } it should "sequence on Var[Seq[T]]" in { @@ -864,7 +917,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { it should "drop" in { var triggers1 = List.empty[Int] val variable1 = Var(1) - val variable1Logged = variable1.drop(1).tap(triggers1 ::= _) + val variable1Logged = variable1.drop(1).tap(triggers1 ::= _).toRx triggers1 shouldBe List.empty variable1Logged.nowIfSubscribedOption() shouldBe None @@ -949,6 +1002,112 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { triggerRxCount shouldBe 3 } + it should "combine rx and rxlater in Rx" in { + var triggers1 = List.empty[Int] + var triggers2 = List.empty[Int] + var triggerRxCount = 0 + + val variable1 = VarLater[Int]() + val variable2 = Var(1) + val variable1Logged = variable1.tap(triggers1 ::= _) + val variable2Logged = variable2.tap(triggers2 ::= _) + + val mapped = Rx { + triggerRxCount += 1 + variable1Logged.toRx().getOrElse(0) + variable2Logged() + } + + triggers1 shouldBe List.empty + triggers2 shouldBe List.empty + triggerRxCount shouldBe 0 + + mapped.nowIfSubscribedOption() shouldBe None + mapped.now() shouldBe 1 + mapped.nowIfSubscribedOption() shouldBe None + + triggers1 shouldBe List.empty + triggers2 shouldBe List(1) + triggerRxCount shouldBe 1 + + mapped.nowIfSubscribedOption() shouldBe None + mapped.now() shouldBe 1 + mapped.nowIfSubscribedOption() shouldBe None + + variable1.set(10) + + triggers1 shouldBe List.empty + triggers2 shouldBe List(1, 1) + triggerRxCount shouldBe 2 + + mapped.nowIfSubscribedOption() shouldBe None + mapped.now() shouldBe 11 + mapped.nowIfSubscribedOption() shouldBe None + + triggers1 shouldBe List(10) + triggers2 shouldBe List(1, 1, 1) + triggerRxCount shouldBe 3 + + val cancelable = mapped.unsafeSubscribe() + + triggers1 shouldBe List(10, 10) + triggers2 shouldBe List(1, 1, 1, 1) + triggerRxCount shouldBe 4 + + mapped.nowIfSubscribedOption() shouldBe Some(11) + mapped.now() shouldBe 11 + mapped.nowIfSubscribedOption() shouldBe Some(11) + + variable1.set(20) + + triggers1 shouldBe List(20, 10, 10) + triggers2 shouldBe List(1, 1, 1, 1) + triggerRxCount shouldBe 5 + + mapped.nowIfSubscribedOption() shouldBe Some(21) + mapped.now() shouldBe 21 + mapped.nowIfSubscribedOption() shouldBe Some(21) + + variable2.set(2) + + triggers1 shouldBe List(20, 10, 10) + triggers2 shouldBe List(2, 1, 1, 1, 1) + triggerRxCount shouldBe 6 + + mapped.nowIfSubscribedOption() shouldBe Some(22) + mapped.now() shouldBe 22 + mapped.nowIfSubscribedOption() shouldBe Some(22) + + variable2.set(3) + + mapped.nowIfSubscribedOption() shouldBe Some(23) + mapped.now() shouldBe 23 + mapped.nowIfSubscribedOption() shouldBe Some(23) + + triggers1 shouldBe List(20, 10, 10) + triggers2 shouldBe List(3, 2, 1, 1, 1, 1) + triggerRxCount shouldBe 7 + + variable1.set(30) + + mapped.nowIfSubscribedOption() shouldBe Some(33) + mapped.now() shouldBe 33 + mapped.nowIfSubscribedOption() shouldBe Some(33) + + triggers1 shouldBe List(30, 20, 10, 10) + triggers2 shouldBe List(3, 2, 1, 1, 1, 1) + triggerRxCount shouldBe 8 + + cancelable.unsafeCancel() + + mapped.nowIfSubscribedOption() shouldBe None + mapped.now() shouldBe 33 + mapped.nowIfSubscribedOption() shouldBe None + + triggers1 shouldBe List(30, 30, 20, 10, 10) + triggers2 shouldBe List(3, 3, 2, 1, 1, 1, 1) + triggerRxCount shouldBe 9 + } + it should "now() in Rx, and owners with lazy subscriptions" in { var triggers1 = List.empty[Int] var triggers2 = List.empty[Int] From bedefe66b02cf399b16ca6bfdfc997b9e8a6fd29 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 16 Jan 2023 19:04:28 +0100 Subject: [PATCH 23/44] again --- .../scala/colibri/reactive/Reactive.scala | 55 ++++++++----------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index b3fe4e94..10c731d5 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -232,45 +232,44 @@ object RxWriter { } trait VarEvent[A] extends RxWriter[A] with RxEvent[A] { - final def transformVarEvent[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = VarEvent.from(g(this), f(this)) - final def transformVarEventRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.from(g(this), this) - final def transformVarEventRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.fromStateless(this, f(this)) + final def transformVarEvent[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = VarEvent.createStateful(g(this), f(this)) + final def transformVarEventRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.createStateful(g(this), this) + final def transformVarEventRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.createStateless(this, f(this)) } object VarEvent { def apply[A](): VarEvent[A] = new VarEventSubject - def subject[A](read: Subject[A]): VarEvent[A] = fromStateless(RxEvent.observable(read), RxWriter.observer(read)) + def subject[A](read: Subject[A]): VarEvent[A] = createStateless(RxEvent.observable(read), RxWriter.observer(read)) - def from[A](read: RxEvent[A], write: RxWriter[A]): VarEvent[A] = new VarEventFromStateful(read, write) - def fromStateless[A](read: RxEvent[A], write: RxWriter[A]): VarEvent[A] = new VarEventFromStateless(read, write) + def create[A](write: RxWriter[A], read: RxEvent[A]): VarEvent[A] = new VarEventCreate(write, read) } trait VarState[A] extends RxWriter[A] with RxState[A] trait VarLater[A] extends VarState[A] with RxLater[A] { final def transformVarLater[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = - VarLater.from(g(this), f(this)) - final def transformVarLaterRx(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.from(g(this), this) - final def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.from(this, f(this)) + VarLater.createStateful(g(this), f(this)) + final def transformVarLaterRx(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.createStateful(g(this), this) + final def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.createStateful(this, f(this)) } object VarLater { def apply[A](): VarLater[A] = new VarLaterSubject - def subject[A](read: Subject[A]): VarLater[A] = fromStateless(RxLater.observable(read), RxWriter.observer(read)) + def subject[A](read: Subject[A]): VarLater[A] = createStateless(RxLater.observable(read), RxWriter.observer(read)) - def from[A](read: RxLater[A], write: RxWriter[A]): VarLater[A] = new VarLaterFromStateful(read, write) - def fromStateless[A](read: RxLater[A], write: RxWriter[A]): VarLater[A] = new VarLaterFromStateless(read, write) + def createStateful[A](write: RxWriter[A], read: RxLater[A]): VarLater[A] = new VarLaterCreateStateful(write, read) + def createStateless[A](write: RxWriter[A], read: RxLater[A]): VarLater[A] = new VarLaterCreateStateless(write, read) } trait Var[A] extends VarState[A] with Rx[A] { final def updateIfSubscribed(f: A => A): Unit = set(f(nowIfSubscribed())) final def update(f: A => A): Unit = set(f(now())) - final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: Rx[A] => Rx[A2]): Var[A2] = Var.from(g(this), f(this)) - final def transformVarRx(g: Rx[A] => Rx[A]): Var[A] = Var.from(g(this), this) - final def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.fromStateless(this, f(this)) + final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: Rx[A] => Rx[A2]): Var[A2] = Var.createStateless(f(this), g(this)) + final def transformVarRx(g: Rx[A] => Rx[A]): Var[A] = Var.createStateless(this, g(this)) + final def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.createStateless(f(this), this) def imap[A2](f: A2 => A)(g: A => A2): Var[A2] = transformVar(_.contramap(f))(_.map(g)) def lens[B](read: A => B)(write: (A, B) => A): Var[B] = @@ -293,10 +292,10 @@ trait Var[A] extends VarState[A] with Rx[A] { object Var { def apply[A](seed: A): Var[A] = new VarSubject(seed) - def subjectSync[A](read: Subject[A]): Var[A] = fromStateless(Rx.observableSync(read), RxWriter.observer(read)) + def subjectSync[A](read: Subject[A]): Var[A] = createStateless(Rx.observableSync(read), RxWriter.observer(read)) - def from[A](read: Rx[A], write: RxWriter[A]): Var[A] = new VarFromStateful(read, write) - def fromStateless[A](read: Rx[A], write: RxWriter[A]): Var[A] = new VarFromStateless(read, write) + def createStateful[A](write: RxWriter[A], read: Rx[A]): Var[A] = new VarCreateStateful(write, rread) + def createStateless[A](write: RxWriter[A], read: Rx[A]): Var[A] = new VarCreateStateless(write, rread) @inline implicit class SeqVarOperations[A](rxvar: Var[Seq[A]]) { def sequence: Rx[Seq[Var[A]]] = Rx.observableSync(new Observable[Seq[Var[A]]] { @@ -315,7 +314,7 @@ object Var { sink.unsafeOnError(error) } } - Var.fromStateless(Rx.const(a), RxWriter.observer(observer)) + Var.createStateless(Rx.const(a), RxWriter.observer(observer)) }) }, sink.unsafeOnError, @@ -409,18 +408,11 @@ private final class VarEventSubject[A] extends VarEvent[A] { def observer: Observer[A] = state } -private final class VarEventFromStateless[A](innerRead: RxEvent[A], innerWrite: RxWriter[A]) extends VarEvent[A] { +private final class VarEventCreate[A](innerWrite: RxWriter[A], innerRead: RxEvent[A]) extends VarEvent[A] { def observable = innerRead.observable def observer = innerWrite.observer } -private final class VarEventFromStateful[A](innerRead: RxEvent[A], innerWrite: RxWriter[A]) extends VarEvent[A] { - private val state = Subject.publish[A]() - - val observable = innerRead.observable.subscribing(state.via(innerWrite.observer)).multicast(state).refCount - def observer = state -} - private final class VarLaterSubject[A] extends VarLater[A] { private val state = Subject.replayLatest[A]() @@ -428,12 +420,12 @@ private final class VarLaterSubject[A] extends VarLater[A] { def observer: Observer[A] = state } -private final class VarLaterFromStateless[A](innerRead: RxLater[A], innerWrite: RxWriter[A]) extends VarLater[A] { +private final class VarLaterCreateStateless[A](innerWrite: RxWriter[A], innerRead: RxLater[A]) extends VarLater[A] { def observable = innerRead.observable def observer = innerWrite.observer } -private final class VarLaterFromStateful[A](innerRead: RxLater[A], innerWrite: RxWriter[A]) extends VarLater[A] { +private final class VarLaterCreateStateful[A](innerWrite: RxWriter[A], innerRead: RxLater[A]) extends VarLater[A] { private val state = Subject.replayLatest[A]() val observable = innerRead.observable.subscribing(state.via(innerWrite.observer)).multicast(state).refCount @@ -451,7 +443,7 @@ private final class VarSubject[A](seed: A) extends Var[A] { def nowIfSubscribedOption() = Some(state.now()) } -private final class VarFromStateless[A](innerRead: Rx[A], innerWrite: RxWriter[A]) extends Var[A] { +private final class VarCreateStateless[A](innerWrite: RxWriter[A], innerRead: Rx[A]) extends Var[A] { val observable = innerRead.observable val observer = innerWrite.observer @@ -460,7 +452,7 @@ private final class VarFromStateless[A](innerRead: Rx[A], innerWrite: RxWriter[A def nowIfSubscribedOption() = innerRead.nowIfSubscribedOption() } -private final class VarFromStateful[A](innerRead: Rx[A], innerWrite: RxWriter[A]) extends Var[A] { +private final class VarCreateStateful[A](innerWrite: RxWriter[A], innerRead: Rx[A]) extends Var[A] { private val state = Subject.replayLatest[A]() val observable = innerRead.observable.subscribing(state.via(innerWrite.observer)).multicast(state).refCount @@ -470,3 +462,4 @@ private final class VarFromStateful[A](innerRead: Rx[A], innerWrite: RxWriter[A] def now()(implicit owner: NowOwner) = owner.unsafeNow(this) def nowIfSubscribedOption() = state.now() } + From 3ea7b6e594e38964644be05d822b989d401eba83 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 16 Jan 2023 19:10:33 +0100 Subject: [PATCH 24/44] ok --- .../scala/colibri/reactive/Reactive.scala | 24 ++++++------ .../src/test/scala/colibri/ReactiveSpec.scala | 37 ++++++++++++++++--- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 10c731d5..eeab8d05 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -232,15 +232,15 @@ object RxWriter { } trait VarEvent[A] extends RxWriter[A] with RxEvent[A] { - final def transformVarEvent[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = VarEvent.createStateful(g(this), f(this)) - final def transformVarEventRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.createStateful(g(this), this) - final def transformVarEventRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.createStateless(this, f(this)) + final def transformVarEvent[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = VarEvent.create(f(this), g(this)) + final def transformVarEventRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.create(this, g(this)) + final def transformVarEventRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.create(f(this), this) } object VarEvent { def apply[A](): VarEvent[A] = new VarEventSubject - def subject[A](read: Subject[A]): VarEvent[A] = createStateless(RxEvent.observable(read), RxWriter.observer(read)) + def subject[A](read: Subject[A]): VarEvent[A] = create(RxWriter.observer(read), RxEvent.observable(read)) def create[A](write: RxWriter[A], read: RxEvent[A]): VarEvent[A] = new VarEventCreate(write, read) } @@ -249,15 +249,15 @@ trait VarState[A] extends RxWriter[A] with RxState[A] trait VarLater[A] extends VarState[A] with RxLater[A] { final def transformVarLater[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = - VarLater.createStateful(g(this), f(this)) - final def transformVarLaterRx(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.createStateful(g(this), this) - final def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.createStateful(this, f(this)) + VarLater.createStateless(f(this), g(this)) + final def transformVarLaterRx(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.createStateless(this, g(this)) + final def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.createStateless(f(this), this) } object VarLater { def apply[A](): VarLater[A] = new VarLaterSubject - def subject[A](read: Subject[A]): VarLater[A] = createStateless(RxLater.observable(read), RxWriter.observer(read)) + def subject[A](read: Subject[A]): VarLater[A] = createStateless(RxWriter.observer(read), RxLater.observable(read)) def createStateful[A](write: RxWriter[A], read: RxLater[A]): VarLater[A] = new VarLaterCreateStateful(write, read) def createStateless[A](write: RxWriter[A], read: RxLater[A]): VarLater[A] = new VarLaterCreateStateless(write, read) @@ -292,10 +292,10 @@ trait Var[A] extends VarState[A] with Rx[A] { object Var { def apply[A](seed: A): Var[A] = new VarSubject(seed) - def subjectSync[A](read: Subject[A]): Var[A] = createStateless(Rx.observableSync(read), RxWriter.observer(read)) + def subjectSync[A](read: Subject[A]): Var[A] = createStateless(RxWriter.observer(read), Rx.observableSync(read)) - def createStateful[A](write: RxWriter[A], read: Rx[A]): Var[A] = new VarCreateStateful(write, rread) - def createStateless[A](write: RxWriter[A], read: Rx[A]): Var[A] = new VarCreateStateless(write, rread) + def createStateful[A](write: RxWriter[A], read: Rx[A]): Var[A] = new VarCreateStateful(write, read) + def createStateless[A](write: RxWriter[A], read: Rx[A]): Var[A] = new VarCreateStateless(write, read) @inline implicit class SeqVarOperations[A](rxvar: Var[Seq[A]]) { def sequence: Rx[Seq[Var[A]]] = Rx.observableSync(new Observable[Seq[Var[A]]] { @@ -314,7 +314,7 @@ object Var { sink.unsafeOnError(error) } } - Var.createStateless(Rx.const(a), RxWriter.observer(observer)) + Var.createStateless(RxWriter.observer(observer), Rx.const(a)) }) }, sink.unsafeOnError, diff --git a/reactive/src/test/scala/colibri/ReactiveSpec.scala b/reactive/src/test/scala/colibri/ReactiveSpec.scala index 96e6e534..35be9c52 100644 --- a/reactive/src/test/scala/colibri/ReactiveSpec.scala +++ b/reactive/src/test/scala/colibri/ReactiveSpec.scala @@ -763,12 +763,11 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { } } - it should "transform and keep state" in { + it should "transform stateful" in { val original: Var[Int] = Var(1) - val encoded: Var[String] = original.transformVar[String]( - _.contramapIterable(str => str.toIntOption) - )( - _.map(num => num.toString) + val encoded: Var[String] = Var.createStateful( + original.contramapIterable(str => str.toIntOption), + original.map(num => num.toString), ) encoded.unsafeSubscribe() @@ -792,6 +791,34 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { encoded.nowIfSubscribed() shouldBe "nope" } + it should "transform stateless" in { + val original: Var[Int] = Var(1) + val encoded: Var[String] = Var.createStateless( + original.contramapIterable(str => str.toIntOption), + original.map(num => num.toString), + ) + + encoded.unsafeSubscribe() + + original.nowIfSubscribed() shouldBe 1 + encoded.nowIfSubscribed() shouldBe "1" + + original.set(2) + + original.nowIfSubscribed() shouldBe 2 + encoded.nowIfSubscribed() shouldBe "2" + + encoded.set("3") + + original.nowIfSubscribed() shouldBe 3 + encoded.nowIfSubscribed() shouldBe "3" + + encoded.set("nope") + + original.nowIfSubscribed() shouldBe 3 + encoded.nowIfSubscribed() shouldBe "3" + } + it should "lens" in { val a: Var[(Int, String)] = Var((0, "Wurst")) val b: Var[String] = a.lens(_._2)((a, b) => a.copy(_2 = b)) From 8bbd80ba946855b549366b4483fdd4e5902947dc Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 16 Jan 2023 20:59:21 +0100 Subject: [PATCH 25/44] readme --- README.md | 54 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index d1dd913a..73592674 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,44 @@ println(variable2.now()) // "Foo" println(rx.now()) // "3 - Foo" ``` -[Outwatch](https://github.com/outwatch/outwatch) works perfectly with Rx - just like Observable. +Apart from `Rx` which always has an initial value, there is `RxLater` which will eventually have a value (both extend RxState which extends RxSource). It also meant for representing state just without an initial state. It is lazy, distinct and has shared execution just like `Rx`. + +``` +import colibri.reactive._ + +val variable = VarLater[Int]() + +val stream1 = RxLater.empty +val stream2 = RxLater.future(Future.successful(1)).map(_ + 1) + +val cancelable = variable.unsafeForeach(println(_)) +val cancelable1 = stream1.unsafeForeach(println(_)) +val cancelable2 = stream2.unsafeForeach(println(_)) + +println(variable.toRx.now()) // None +println(stream1.toRx.now()) // None +println(stream2.toRx.now()) // Some(2) + +variable.set(13) + +println(variable.toRx.now()) // Some(13) +``` + +There also exist `RxEvent` and `VarEvent`, which are event observables with shared execution. That is they behave like `Rx` and `Var` such that transformations are only applied once and not per subscription. But `RxEvent` and `VarEvent` are not distinct and have no current value. They should be used for event streams. + +``` +import colibri.reactive._ + +val variable = VarEvent[Int]() + +val stream = RxEvent.empty + +val mapped = RxEvent.merge(variable.tap(println(_)).map(_ + 1), stream) + +val cancelable = mapped.unsafeForeach(println(_)) +``` + +[Outwatch](https://github.com/outwatch/outwatch) works perfectly with Rx (or RxLater, RxEvent which all extend RxSource) - just like Observable. ```scala @@ -222,21 +259,6 @@ val component: VModifier = { } ``` -There also exist `RxEvent` and `VarEvent`, which are event observables with shared execution. That is they behave like `Rx` and `Var` such that transformations are only applied once and not per subscription. But `RxEvent` and `VarEvent` are not distinct and have no current value. They should be used for event streams. - -``` -import colibri.reactive._ - -val variable = VarEvent[Int]() - -val stream1 = RxEvent.empty -val stream2 = RxEvent.const(1) - -val mapped = RxEvent.merge(variable.tap(println(_)).map(_ + 1), stream1, stream2) - -val cancelable = mapped.unsafeForeach(println(_)) -``` - ### Memory management The same principles as for Observables hold. Any cancelable that is returned from the API needs to be handled by the the caller. Best practice: use subscribe/foreach as seldomly as possible - only in selected spots or within a library. From 0a911986751e30035d4724e98fc46dcb5e17588d Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Tue, 17 Jan 2023 01:43:52 +0100 Subject: [PATCH 26/44] generic self types --- .../scala/colibri/reactive/Reactive.scala | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index eeab8d05..6fca3946 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -15,10 +15,7 @@ object RxMissingNowException "Missing current value inside an RxState (Rx or RxLater). Make sure, the RxState has active subscriptions when calling nowIfSubscribed.", ) -trait RxSource[+A] { - type Self[+X] <: RxSource[X] - type SelfSync[+X] <: RxSource[X] - +trait RxSourceSelf[+Self[+X] <: RxSource[X], +SelfSync[+X] <: RxSource[X], +A] { def observable: Observable[A] def selfRxSourceSync: SelfSync[A] @@ -72,33 +69,32 @@ trait RxSource[+A] { transformRxSource(_.withLatest(sourceB.observable)) } -object RxSource { +object RxSourceSelf { implicit object source extends Source[RxSource] { def unsafeSubscribe[A](source: RxSource[A])(sink: Observer[A]): Cancelable = source.observable.unsafeSubscribe(sink) } - @inline implicit final class RxSourceOps[A](val self: RxSource[A]) extends AnyVal { - def scan[B](seed: => B)(f: (B, A) => B): self.SelfSync[B] = self.transformRxSourceSync(_.scan(seed)(f)) + @inline implicit final class RxSourceOps[Self[+X] <: RxSource[X], SelfSync[+X] <: RxSource[X], A](val self: RxSourceSelf[Self, SelfSync, A]) extends AnyVal { + def scan[B](seed: => B)(f: (B, A) => B): SelfSync[B] = self.transformRxSourceSync(_.scan(seed)(f)) - def filter(f: A => Boolean): self.Self[A] = self.transformRxSource(_.filter(f)) + def filter(f: A => Boolean): Self[A] = self.transformRxSource(_.filter(f)) } - @inline implicit final class RxSourceBooleanOps(val self: RxSource[Boolean]) extends AnyVal { - @inline def toggle[A](ifTrue: => A, ifFalse: => A): self.SelfSync[A] = self.map { + @inline implicit final class RxSourceBooleanOps[Self[+X] <: RxSource[X], SelfSync[+X] <: RxSource[X]](private val self: RxSourceSelf[Self, SelfSync, Boolean]) extends AnyVal { + @inline def toggle[A](ifTrue: => A, ifFalse: => A): SelfSync[A] = self.map { case true => ifTrue case false => ifFalse } - @inline def toggle[A: Monoid](ifTrue: => A): self.SelfSync[A] = toggle(ifTrue, Monoid[A].empty) + @inline def toggle[A: Monoid](ifTrue: => A): SelfSync[A] = toggle(ifTrue, Monoid[A].empty) - @inline def negated: self.SelfSync[Boolean] = self.map(x => !x) + @inline def negated: SelfSync[Boolean] = self.map(x => !x) } } -trait RxEvent[+A] extends RxSource[A] { - type Self[+X] = RxEvent[X] - type SelfSync[+X] = RxEvent[X] +trait RxSource[+A] extends RxSourceSelf[RxSource, RxSource, A] +trait RxEvent[+A] extends RxSource[A] with RxSourceSelf[RxEvent, RxEvent, A] { final override def selfRxSourceSync: RxEvent[A] = this final override def transformRxSource[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) final override def transformRxSourceSync[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) @@ -123,14 +119,11 @@ object RxEvent extends RxPlatform { private def observableUnshared[A](observable: Observable[A]): RxEvent[A] = new RxEventObservable(observable) } -trait RxState[+A] extends RxSource[A] { - type Self[+X] = RxLater[X] - type SelfSync[+X] <: RxState[X] - +trait RxState[+A] extends RxSource[A] with RxSourceSelf[RxLater, RxState, A] { final override def transformRxSource[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) } -trait RxLater[+A] extends RxState[A] { +trait RxLater[+A] extends RxState[A] with RxSourceSelf[RxLater, RxLater, A] { type SelfSync[+X] = RxLater[X] final override def selfRxSourceSync: RxLater[A] = this @@ -151,7 +144,7 @@ object RxLater { } } -trait Rx[+A] extends RxState[A] { +trait Rx[+A] extends RxState[A] with RxSourceSelf[RxLater, Rx, A] { type SelfSync[+X] = Rx[X] def apply()(implicit owner: LiveOwner): A @@ -271,21 +264,21 @@ trait Var[A] extends VarState[A] with Rx[A] { final def transformVarRx(g: Rx[A] => Rx[A]): Var[A] = Var.createStateless(this, g(this)) final def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.createStateless(f(this), this) - def imap[A2](f: A2 => A)(g: A => A2): Var[A2] = transformVar(_.contramap(f))(_.map(g)) - def lens[B](read: A => B)(write: (A, B) => A): Var[B] = + final def imap[A2](f: A2 => A)(g: A => A2): Var[A2] = transformVar(_.contramap(f))(_.map(g)) + final def lens[B](read: A => B)(write: (A, B) => A): Var[B] = transformVar(_.contramap(write(nowIfSubscribed(), _)))(_.map(read)) - def prism[A2](f: A2 => A)(g: A => Option[A2])(seed: => A2): Var[A2] = + final def prism[A2](f: A2 => A)(g: A => Option[A2])(seed: => A2): Var[A2] = transformVar(_.contramap(f))(rx => Rx.observableSync(rx.observable.mapFilter(g).prependEval(seed))) - def subType[A2 <: A: ClassTag](seed: => A2): Var[A2] = prism[A2]((x: A2) => x) { + final def subType[A2 <: A: ClassTag](seed: => A2): Var[A2] = prism[A2]((x: A2) => x) { case a: A2 => Some(a) case _ => None }(seed) - def imapO[B](optic: Iso[A, B]): Var[B] = imap(optic.reverseGet(_))(optic.get(_)) - def lensO[B](optic: Lens[A, B]): Var[B] = lens(optic.get(_))((base, zoomed) => optic.replace(zoomed)(base)) - def prismO[B](optic: Prism[A, B])(seed: => B): Var[B] = + final def imapO[B](optic: Iso[A, B]): Var[B] = imap(optic.reverseGet(_))(optic.get(_)) + final def lensO[B](optic: Lens[A, B]): Var[B] = lens(optic.get(_))((base, zoomed) => optic.replace(zoomed)(base)) + final def prismO[B](optic: Prism[A, B])(seed: => B): Var[B] = prism(optic.reverseGet(_))(optic.getOption(_))(seed) } From 558d815fefc010b7e675fc9f1dde56c938ebd97d Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Wed, 18 Jan 2023 02:40:27 +0100 Subject: [PATCH 27/44] rename --- .../scala/colibri/reactive/Reactive.scala | 128 ++++++++++-------- 1 file changed, 71 insertions(+), 57 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 6fca3946..f8f3b166 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -18,9 +18,9 @@ object RxMissingNowException trait RxSourceSelf[+Self[+X] <: RxSource[X], +SelfSync[+X] <: RxSource[X], +A] { def observable: Observable[A] - def selfRxSourceSync: SelfSync[A] - def transformRxSource[B](f: Observable[A] => Observable[B]): Self[B] - def transformRxSourceSync[B](f: Observable[A] => Observable[B]): SelfSync[B] + def selfRxSync: SelfSync[A] + def transformRx[B](f: Observable[A] => Observable[B]): Self[B] + def transformRxSync[B](f: Observable[A] => Observable[B]): SelfSync[B] final def subscribe: SyncIO[Cancelable] = observable.subscribeSyncIO @@ -31,42 +31,42 @@ trait RxSourceSelf[+Self[+X] <: RxSource[X], +SelfSync[+X] <: RxSource[X], +A] { final def hot: SyncIO[SelfSync[A]] = SyncIO(unsafeHot()) final def unsafeHot(): SelfSync[A] = { val _ = unsafeSubscribe() - selfRxSourceSync + selfRxSync } - final def map[B](f: A => B): SelfSync[B] = transformRxSourceSync(_.map(f)) - final def tap(f: A => Unit): SelfSync[A] = transformRxSourceSync(_.tap(f)) + final def map[B](f: A => B): SelfSync[B] = transformRxSync(_.map(f)) + final def tap(f: A => Unit): SelfSync[A] = transformRxSync(_.tap(f)) - final def collect[B](f: PartialFunction[A, B]): Self[B] = transformRxSource(_.collect(f)) - final def mapEither[B](f: A => Either[Throwable, B]): Self[B] = transformRxSource(_.mapEither(f)) - final def drop(n: Int): Self[A] = transformRxSource(_.drop(n)) + final def collect[B](f: PartialFunction[A, B]): Self[B] = transformRx(_.collect(f)) + final def mapEither[B](f: A => Either[Throwable, B]): Self[B] = transformRx(_.mapEither(f)) + final def drop(n: Int): Self[A] = transformRx(_.drop(n)) - final def mapSyncEffect[F[_]: RunSyncEffect, B](f: A => F[B]): SelfSync[B] = transformRxSourceSync(_.mapEffect(f)) - final def mapEffect[F[_]: RunEffect, B](f: A => F[B]): Self[B] = transformRxSource(_.mapEffect(f)) - final def mapFuture[B](f: A => Future[B]): Self[B] = transformRxSource(_.mapFuture(f)) + final def mapSyncEffect[F[_]: RunSyncEffect, B](f: A => F[B]): SelfSync[B] = transformRxSync(_.mapEffect(f)) + final def mapEffect[F[_]: RunEffect, B](f: A => F[B]): Self[B] = transformRx(_.mapEffect(f)) + final def mapFuture[B](f: A => Future[B]): Self[B] = transformRx(_.mapFuture(f)) - final def as[B](value: B): SelfSync[B] = transformRxSourceSync(_.as(value)) - final def asEval[B](value: => B): SelfSync[B] = transformRxSourceSync(_.asEval(value)) + final def as[B](value: B): SelfSync[B] = transformRxSync(_.as(value)) + final def asEval[B](value: => B): SelfSync[B] = transformRxSync(_.asEval(value)) - final def asSyncEffect[F[_]: RunSyncEffect, B](value: F[B]): SelfSync[B] = transformRxSourceSync(_.asEffect(value)) - final def asEffect[F[_]: RunEffect, B](value: F[B]): Self[B] = transformRxSource(_.asEffect(value)) - final def asFuture[B](value: => Future[B]): Self[B] = transformRxSource(_.asFuture(value)) + final def asSyncEffect[F[_]: RunSyncEffect, B](value: F[B]): SelfSync[B] = transformRxSync(_.asEffect(value)) + final def asEffect[F[_]: RunEffect, B](value: F[B]): Self[B] = transformRx(_.asEffect(value)) + final def asFuture[B](value: => Future[B]): Self[B] = transformRx(_.asFuture(value)) - final def via(writer: RxWriter[A]): SelfSync[A] = transformRxSourceSync(_.via(writer.observer)) + final def via(writer: RxWriter[A]): SelfSync[A] = transformRxSync(_.via(writer.observer)) - final def switchMap[B](f: A => RxSource[B]): Self[B] = transformRxSource(_.switchMap(f andThen (_.observable))) - final def mergeMap[B](f: A => RxSource[B]): Self[B] = transformRxSource(_.mergeMap(f andThen (_.observable))) - final def concatMap[B](f: A => RxSource[B]): Self[B] = transformRxSource(_.concatMap(f andThen (_.observable))) + final def switchMap[B](f: A => RxSource[B]): Self[B] = transformRx(_.switchMap(f andThen (_.observable))) + final def mergeMap[B](f: A => RxSource[B]): Self[B] = transformRx(_.mergeMap(f andThen (_.observable))) + final def concatMap[B](f: A => RxSource[B]): Self[B] = transformRx(_.concatMap(f andThen (_.observable))) final def combineLatestMap[B, R](sourceB: RxSource[B])(f: (A, B) => R): Self[R] = - transformRxSource(_.combineLatestMap(sourceB.observable)(f)) + transformRx(_.combineLatestMap(sourceB.observable)(f)) final def combineLatest[B](sourceB: RxSource[B]): Self[(A, B)] = - transformRxSource(_.combineLatest(sourceB.observable)) + transformRx(_.combineLatest(sourceB.observable)) final def withLatestMap[B, R](sourceB: RxSource[B])(f: (A, B) => R): Self[R] = - transformRxSource(_.withLatestMap(sourceB.observable)(f)) + transformRx(_.withLatestMap(sourceB.observable)(f)) final def withLatest[B](sourceB: RxSource[B]): Self[(A, B)] = - transformRxSource(_.withLatest(sourceB.observable)) + transformRx(_.withLatest(sourceB.observable)) } object RxSourceSelf { @@ -75,9 +75,9 @@ object RxSourceSelf { } @inline implicit final class RxSourceOps[Self[+X] <: RxSource[X], SelfSync[+X] <: RxSource[X], A](val self: RxSourceSelf[Self, SelfSync, A]) extends AnyVal { - def scan[B](seed: => B)(f: (B, A) => B): SelfSync[B] = self.transformRxSourceSync(_.scan(seed)(f)) + def scan[B](seed: => B)(f: (B, A) => B): SelfSync[B] = self.transformRxSync(_.scan(seed)(f)) - def filter(f: A => Boolean): Self[A] = self.transformRxSource(_.filter(f)) + def filter(f: A => Boolean): Self[A] = self.transformRx(_.filter(f)) } @inline implicit final class RxSourceBooleanOps[Self[+X] <: RxSource[X], SelfSync[+X] <: RxSource[X]](private val self: RxSourceSelf[Self, SelfSync, Boolean]) extends AnyVal { @@ -95,9 +95,9 @@ object RxSourceSelf { trait RxSource[+A] extends RxSourceSelf[RxSource, RxSource, A] trait RxEvent[+A] extends RxSource[A] with RxSourceSelf[RxEvent, RxEvent, A] { - final override def selfRxSourceSync: RxEvent[A] = this - final override def transformRxSource[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) - final override def transformRxSourceSync[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) + final override def selfRxSync: RxEvent[A] = this + final override def transformRx[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) + final override def transformRxSync[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) } object RxEvent extends RxPlatform { @@ -120,14 +120,14 @@ object RxEvent extends RxPlatform { } trait RxState[+A] extends RxSource[A] with RxSourceSelf[RxLater, RxState, A] { - final override def transformRxSource[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) + final override def transformRx[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) } trait RxLater[+A] extends RxState[A] with RxSourceSelf[RxLater, RxLater, A] { type SelfSync[+X] = RxLater[X] - final override def selfRxSourceSync: RxLater[A] = this - final override def transformRxSourceSync[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) + final override def selfRxSync: RxLater[A] = this + final override def transformRxSync[B](f: Observable[A] => Observable[B]): RxLater[B] = RxLater.observable(f(observable)) } object RxLater { @@ -153,8 +153,8 @@ trait Rx[+A] extends RxState[A] with RxSourceSelf[RxLater, Rx, A] { final def nowIfSubscribed(): A = nowIfSubscribedOption().getOrElse(throw RxMissingNowException) - final override def selfRxSourceSync: Rx[A] = this - final override def transformRxSourceSync[B](f: Observable[A] => Observable[B]): Rx[B] = Rx.observableSync(f(observable)) + final override def selfRxSync: Rx[A] = this + final override def transformRxSync[B](f: Observable[A] => Observable[B]): Rx[B] = Rx.observableSync(f(observable)) } object Rx extends RxPlatform { @@ -224,10 +224,12 @@ object RxWriter { } } -trait VarEvent[A] extends RxWriter[A] with RxEvent[A] { - final def transformVarEvent[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = VarEvent.create(f(this), g(this)) - final def transformVarEventRxEvent(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.create(this, g(this)) - final def transformVarEventRxWriter(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.create(f(this), this) +trait VarSource[A] extends RxWriter[A] with RxSource[A] + +trait VarEvent[A] extends VarSource[A] with RxEvent[A] { + final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = VarEvent.create(f(this), g(this)) + final def transformVarRead(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.create(this, g(this)) + final def transformVarWrite(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.create(f(this), this) } object VarEvent { @@ -241,10 +243,10 @@ object VarEvent { trait VarState[A] extends RxWriter[A] with RxState[A] trait VarLater[A] extends VarState[A] with RxLater[A] { - final def transformVarLater[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = + final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = VarLater.createStateless(f(this), g(this)) - final def transformVarLaterRx(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.createStateless(this, g(this)) - final def transformVarLaterRxWriter(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.createStateless(f(this), this) + final def transformVarRead(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.createStateless(this, g(this)) + final def transformVarWrite(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.createStateless(f(this), this) } object VarLater { @@ -261,8 +263,8 @@ trait Var[A] extends VarState[A] with Rx[A] { final def update(f: A => A): Unit = set(f(now())) final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: Rx[A] => Rx[A2]): Var[A2] = Var.createStateless(f(this), g(this)) - final def transformVarRx(g: Rx[A] => Rx[A]): Var[A] = Var.createStateless(this, g(this)) - final def transformVarRxWriter(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.createStateless(f(this), this) + final def transformVarRead(g: Rx[A] => Rx[A]): Var[A] = Var.createStateless(this, g(this)) + final def transformVarWrite(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.createStateless(f(this), this) final def imap[A2](f: A2 => A)(g: A => A2): Var[A2] = transformVar(_.contramap(f))(_.map(g)) final def lens[B](read: A => B)(write: (A, B) => A): Var[B] = @@ -354,11 +356,11 @@ object Var { } } +// RxEvent + private final class RxEventObservable[A](val observable: Observable[A]) extends RxEvent[A] -private object RxLaterEmpty extends RxLater[Nothing] { - def observable = Observable.empty -} +// Rx private final class RxConst[A](value: A) extends Rx[A] { private lazy val someValue = Some(value) @@ -370,30 +372,40 @@ private final class RxConst[A](value: A) extends Rx[A] { def nowIfSubscribedOption(): Option[A] = someValue } -private final class RxLaterWrap[A](state: Rx[A]) extends RxLater[A] { - def observable: Observable[A] = state.observable -} - -private final class RxLaterObservable[A](inner: Observable[A]) extends RxLater[A] { +private final class RxSyncObservable[A](inner: Observable[A]) extends Rx[A] { private val state = Subject.replayLatest[A]() val observable: Observable[A] = inner.dropUntilSyncLatest.distinctOnEquals.tapCancel(state.unsafeResetState).multicast(state).refCount + + def apply()(implicit owner: LiveOwner) = owner.unsafeLive(this) + def now()(implicit owner: NowOwner) = owner.unsafeNow(this) + def nowIfSubscribedOption() = state.now() } -private final class RxSyncObservable[A](inner: Observable[A]) extends Rx[A] { +// RxLater + +private object RxLaterEmpty extends RxLater[Nothing] { + def observable = Observable.empty +} + +private final class RxLaterWrap[A](state: Rx[A]) extends RxLater[A] { + def observable: Observable[A] = state.observable +} + +private final class RxLaterObservable[A](inner: Observable[A]) extends RxLater[A] { private val state = Subject.replayLatest[A]() val observable: Observable[A] = inner.dropUntilSyncLatest.distinctOnEquals.tapCancel(state.unsafeResetState).multicast(state).refCount - - def apply()(implicit owner: LiveOwner) = owner.unsafeLive(this) - def now()(implicit owner: NowOwner) = owner.unsafeNow(this) - def nowIfSubscribedOption() = state.now() } +// RxWriter + private final class RxWriterObserver[A](val observer: Observer[A]) extends RxWriter[A] +// VarEvent + private final class VarEventSubject[A] extends VarEvent[A] { private val state = Subject.publish[A]() @@ -425,6 +437,8 @@ private final class VarLaterCreateStateful[A](innerWrite: RxWriter[A], innerRead def observer = state } +// Var + private final class VarSubject[A](seed: A) extends Var[A] { private val state = Subject.behavior[A](seed) From 248fa06b9988edabdf77ab2e153229d903790e12 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Wed, 18 Jan 2023 02:40:42 +0100 Subject: [PATCH 28/44] format --- .../scala/colibri/reactive/Reactive.scala | 51 ++++++++++--------- .../src/test/scala/colibri/ReactiveSpec.scala | 18 +++---- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index f8f3b166..5ce46ede 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -74,13 +74,17 @@ object RxSourceSelf { def unsafeSubscribe[A](source: RxSource[A])(sink: Observer[A]): Cancelable = source.observable.unsafeSubscribe(sink) } - @inline implicit final class RxSourceOps[Self[+X] <: RxSource[X], SelfSync[+X] <: RxSource[X], A](val self: RxSourceSelf[Self, SelfSync, A]) extends AnyVal { + @inline implicit final class RxSourceOps[Self[+X] <: RxSource[X], SelfSync[+X] <: RxSource[X], A]( + val self: RxSourceSelf[Self, SelfSync, A], + ) extends AnyVal { def scan[B](seed: => B)(f: (B, A) => B): SelfSync[B] = self.transformRxSync(_.scan(seed)(f)) def filter(f: A => Boolean): Self[A] = self.transformRx(_.filter(f)) } - @inline implicit final class RxSourceBooleanOps[Self[+X] <: RxSource[X], SelfSync[+X] <: RxSource[X]](private val self: RxSourceSelf[Self, SelfSync, Boolean]) extends AnyVal { + @inline implicit final class RxSourceBooleanOps[Self[+X] <: RxSource[X], SelfSync[+X] <: RxSource[X]]( + private val self: RxSourceSelf[Self, SelfSync, Boolean], + ) extends AnyVal { @inline def toggle[A](ifTrue: => A, ifFalse: => A): SelfSync[A] = self.map { case true => ifTrue case false => ifFalse @@ -228,8 +232,8 @@ trait VarSource[A] extends RxWriter[A] with RxSource[A] trait VarEvent[A] extends VarSource[A] with RxEvent[A] { final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: RxEvent[A] => RxEvent[A2]): VarEvent[A2] = VarEvent.create(f(this), g(this)) - final def transformVarRead(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.create(this, g(this)) - final def transformVarWrite(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.create(f(this), this) + final def transformVarRead(g: RxEvent[A] => RxEvent[A]): VarEvent[A] = VarEvent.create(this, g(this)) + final def transformVarWrite(f: RxWriter[A] => RxWriter[A]): VarEvent[A] = VarEvent.create(f(this), this) } object VarEvent { @@ -245,8 +249,8 @@ trait VarState[A] extends RxWriter[A] with RxState[A] trait VarLater[A] extends VarState[A] with RxLater[A] { final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = VarLater.createStateless(f(this), g(this)) - final def transformVarRead(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.createStateless(this, g(this)) - final def transformVarWrite(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.createStateless(f(this), this) + final def transformVarRead(g: RxLater[A] => RxLater[A]): VarLater[A] = VarLater.createStateless(this, g(this)) + final def transformVarWrite(f: RxWriter[A] => RxWriter[A]): VarLater[A] = VarLater.createStateless(f(this), this) } object VarLater { @@ -254,7 +258,7 @@ object VarLater { def subject[A](read: Subject[A]): VarLater[A] = createStateless(RxWriter.observer(read), RxLater.observable(read)) - def createStateful[A](write: RxWriter[A], read: RxLater[A]): VarLater[A] = new VarLaterCreateStateful(write, read) + def createStateful[A](write: RxWriter[A], read: RxLater[A]): VarLater[A] = new VarLaterCreateStateful(write, read) def createStateless[A](write: RxWriter[A], read: RxLater[A]): VarLater[A] = new VarLaterCreateStateless(write, read) } @@ -263,8 +267,8 @@ trait Var[A] extends VarState[A] with Rx[A] { final def update(f: A => A): Unit = set(f(now())) final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: Rx[A] => Rx[A2]): Var[A2] = Var.createStateless(f(this), g(this)) - final def transformVarRead(g: Rx[A] => Rx[A]): Var[A] = Var.createStateless(this, g(this)) - final def transformVarWrite(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.createStateless(f(this), this) + final def transformVarRead(g: Rx[A] => Rx[A]): Var[A] = Var.createStateless(this, g(this)) + final def transformVarWrite(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.createStateless(f(this), this) final def imap[A2](f: A2 => A)(g: A => A2): Var[A2] = transformVar(_.contramap(f))(_.map(g)) final def lens[B](read: A => B)(write: (A, B) => A): Var[B] = @@ -289,7 +293,7 @@ object Var { def subjectSync[A](read: Subject[A]): Var[A] = createStateless(RxWriter.observer(read), Rx.observableSync(read)) - def createStateful[A](write: RxWriter[A], read: Rx[A]): Var[A] = new VarCreateStateful(write, read) + def createStateful[A](write: RxWriter[A], read: Rx[A]): Var[A] = new VarCreateStateful(write, read) def createStateless[A](write: RxWriter[A], read: Rx[A]): Var[A] = new VarCreateStateless(write, read) @inline implicit class SeqVarOperations[A](rxvar: Var[Seq[A]]) { @@ -379,8 +383,8 @@ private final class RxSyncObservable[A](inner: Observable[A]) extends Rx[A] { inner.dropUntilSyncLatest.distinctOnEquals.tapCancel(state.unsafeResetState).multicast(state).refCount def apply()(implicit owner: LiveOwner) = owner.unsafeLive(this) - def now()(implicit owner: NowOwner) = owner.unsafeNow(this) - def nowIfSubscribedOption() = state.now() + def now()(implicit owner: NowOwner) = owner.unsafeNow(this) + def nowIfSubscribedOption() = state.now() } // RxLater @@ -407,7 +411,7 @@ private final class RxWriterObserver[A](val observer: Observer[A]) extends RxWri // VarEvent private final class VarEventSubject[A] extends VarEvent[A] { - private val state = Subject.publish[A]() + private val state = Subject.publish[A]() def observable: Observable[A] = state def observer: Observer[A] = state @@ -433,8 +437,8 @@ private final class VarLaterCreateStateless[A](innerWrite: RxWriter[A], innerRea private final class VarLaterCreateStateful[A](innerWrite: RxWriter[A], innerRead: RxLater[A]) extends VarLater[A] { private val state = Subject.replayLatest[A]() - val observable = innerRead.observable.subscribing(state.via(innerWrite.observer)).multicast(state).refCount - def observer = state + val observable = innerRead.observable.subscribing(state.via(innerWrite.observer)).multicast(state).refCount + def observer = state } // Var @@ -446,8 +450,8 @@ private final class VarSubject[A](seed: A) extends Var[A] { def observer: Observer[A] = state def apply()(implicit owner: LiveOwner) = owner.unsafeLive(this) - def now()(implicit owner: NowOwner) = state.now() - def nowIfSubscribedOption() = Some(state.now()) + def now()(implicit owner: NowOwner) = state.now() + def nowIfSubscribedOption() = Some(state.now()) } private final class VarCreateStateless[A](innerWrite: RxWriter[A], innerRead: Rx[A]) extends Var[A] { @@ -455,18 +459,17 @@ private final class VarCreateStateless[A](innerWrite: RxWriter[A], innerRead: Rx val observer = innerWrite.observer def apply()(implicit owner: LiveOwner) = innerRead() - def now()(implicit owner: NowOwner) = innerRead.now() - def nowIfSubscribedOption() = innerRead.nowIfSubscribedOption() + def now()(implicit owner: NowOwner) = innerRead.now() + def nowIfSubscribedOption() = innerRead.nowIfSubscribedOption() } private final class VarCreateStateful[A](innerWrite: RxWriter[A], innerRead: Rx[A]) extends Var[A] { private val state = Subject.replayLatest[A]() - val observable = innerRead.observable.subscribing(state.via(innerWrite.observer)).multicast(state).refCount - def observer = state + val observable = innerRead.observable.subscribing(state.via(innerWrite.observer)).multicast(state).refCount + def observer = state def apply()(implicit owner: LiveOwner) = owner.unsafeLive(this) - def now()(implicit owner: NowOwner) = owner.unsafeNow(this) - def nowIfSubscribedOption() = state.now() + def now()(implicit owner: NowOwner) = owner.unsafeNow(this) + def nowIfSubscribedOption() = state.now() } - diff --git a/reactive/src/test/scala/colibri/ReactiveSpec.scala b/reactive/src/test/scala/colibri/ReactiveSpec.scala index 35be9c52..f7e8760c 100644 --- a/reactive/src/test/scala/colibri/ReactiveSpec.scala +++ b/reactive/src/test/scala/colibri/ReactiveSpec.scala @@ -593,8 +593,8 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { it should "collect initial some" in { var collectedStates = Vector.empty[Int] - val variable = Var[Option[Int]](Some(1)) - val collected = variable.collect { case Some(x) => x }.toRx(0) + val variable = Var[Option[Int]](Some(1)) + val collected = variable.collect { case Some(x) => x }.toRx(0) collected.unsafeForeach(collectedStates :+= _) @@ -613,8 +613,8 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { it should "collect initial none" in { var collectedStates = Vector.empty[Int] - val variable = Var[Option[Int]](None) - val collected = variable.collect { case Some(x) => x }.toRx(0) + val variable = Var[Option[Int]](None) + val collected = variable.collect { case Some(x) => x }.toRx(0) collected.unsafeForeach(collectedStates :+= _) @@ -631,11 +631,11 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { } it should "collect later initial none" in { - var tapStates = Vector.empty[Int] + var tapStates = Vector.empty[Int] var collectedStates = Vector.empty[Int] - val variable = Var[Option[Int]](None) - val collected = variable.collect { case Some(x) => x }.tap(tapStates :+= _) + val variable = Var[Option[Int]](None) + val collected = variable.collect { case Some(x) => x }.tap(tapStates :+= _) val cancelable = collected.unsafeForeach(collectedStates :+= _) @@ -764,7 +764,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { } it should "transform stateful" in { - val original: Var[Int] = Var(1) + val original: Var[Int] = Var(1) val encoded: Var[String] = Var.createStateful( original.contramapIterable(str => str.toIntOption), original.map(num => num.toString), @@ -792,7 +792,7 @@ class ReactiveSpec extends AsyncFlatSpec with Matchers { } it should "transform stateless" in { - val original: Var[Int] = Var(1) + val original: Var[Int] = Var(1) val encoded: Var[String] = Var.createStateless( original.contramapIterable(str => str.toIntOption), original.map(num => num.toString), From 6dd2d50dc2a2a1d35b1f4d99fe4a67666574d6c1 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Wed, 18 Jan 2023 02:42:10 +0100 Subject: [PATCH 29/44] fix rxsource object --- .../src/main/scala/colibri/reactive/Reactive.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 5ce46ede..f70ab36b 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -70,10 +70,6 @@ trait RxSourceSelf[+Self[+X] <: RxSource[X], +SelfSync[+X] <: RxSource[X], +A] { } object RxSourceSelf { - implicit object source extends Source[RxSource] { - def unsafeSubscribe[A](source: RxSource[A])(sink: Observer[A]): Cancelable = source.observable.unsafeSubscribe(sink) - } - @inline implicit final class RxSourceOps[Self[+X] <: RxSource[X], SelfSync[+X] <: RxSource[X], A]( val self: RxSourceSelf[Self, SelfSync, A], ) extends AnyVal { @@ -98,6 +94,12 @@ object RxSourceSelf { trait RxSource[+A] extends RxSourceSelf[RxSource, RxSource, A] +object RxSource { + implicit object source extends Source[RxSource] { + def unsafeSubscribe[A](source: RxSource[A])(sink: Observer[A]): Cancelable = source.observable.unsafeSubscribe(sink) + } +} + trait RxEvent[+A] extends RxSource[A] with RxSourceSelf[RxEvent, RxEvent, A] { final override def selfRxSync: RxEvent[A] = this final override def transformRx[B](f: Observable[A] => Observable[B]): RxEvent[B] = RxEvent.observable(f(observable)) From b514a8f88046a267441109f71d39ce179889d735 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Wed, 18 Jan 2023 02:50:32 +0100 Subject: [PATCH 30/44] methods --- .../main/scala/colibri/reactive/Reactive.scala | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index f70ab36b..59390d89 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -123,6 +123,13 @@ object RxEvent extends RxPlatform { def observable[A](observable: Observable[A]): RxEvent[A] = observableUnshared(observable.publish.refCount) private def observableUnshared[A](observable: Observable[A]): RxEvent[A] = new RxEventObservable(observable) + + @inline implicit final class RxEventOps[A](private val self: RxEvent[A]) extends AnyVal { + def toRxLater: RxLater[A] = RxLater.observable(self.observable) + + def toRx: Rx[Option[A]] = Rx.observableSeed(self.observable.map[Option[A]](Some.apply))(None) + def toRx(seed: => A): Rx[A] = Rx.observableSeed(self.observable)(seed) + } } trait RxState[+A] extends RxSource[A] with RxSourceSelf[RxLater, RxState, A] { @@ -144,7 +151,11 @@ object RxLater { def observable[A](observable: Observable[A]): RxLater[A] = new RxLaterObservable(observable) + def wrap[A](rx: Rx[A]): RxLater[A] = new RxLaterWrap(rx) + @inline implicit final class RxLaterOps[A](private val self: RxLater[A]) extends AnyVal { + def toRxEvent: RxEvent[A] = RxEvent.observable(self.observable) + def toRx: Rx[Option[A]] = Rx.observableSeed(self.observable.map[Option[A]](Some.apply))(None) def toRx(seed: => A): Rx[A] = Rx.observableSeed(self.observable)(seed) } @@ -194,7 +205,9 @@ object Rx extends RxPlatform { def observableSync[A](observable: Observable[A]): Rx[A] = new RxSyncObservable(observable) @inline implicit final class RxLaterOps[A](private val self: Rx[A]) extends AnyVal { - def toRxLater: RxLater[A] = new RxLaterWrap(self) + def toRxEvent: RxEvent[A] = RxEvent.observable(self.observable) + + def toRxLater: RxLater[A] = RxLater.wrap(self) } } From b9b874658a09f2d9a37ec8318217b4033611a116 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Fri, 20 Jan 2023 01:41:36 +0100 Subject: [PATCH 31/44] format --- reactive/src/main/scala/colibri/reactive/Reactive.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 59390d89..1a43e477 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -154,7 +154,7 @@ object RxLater { def wrap[A](rx: Rx[A]): RxLater[A] = new RxLaterWrap(rx) @inline implicit final class RxLaterOps[A](private val self: RxLater[A]) extends AnyVal { - def toRxEvent: RxEvent[A] = RxEvent.observable(self.observable) + def toRxEvent: RxEvent[A] = RxEvent.observable(self.observable) def toRx: Rx[Option[A]] = Rx.observableSeed(self.observable.map[Option[A]](Some.apply))(None) def toRx(seed: => A): Rx[A] = Rx.observableSeed(self.observable)(seed) @@ -205,7 +205,7 @@ object Rx extends RxPlatform { def observableSync[A](observable: Observable[A]): Rx[A] = new RxSyncObservable(observable) @inline implicit final class RxLaterOps[A](private val self: Rx[A]) extends AnyVal { - def toRxEvent: RxEvent[A] = RxEvent.observable(self.observable) + def toRxEvent: RxEvent[A] = RxEvent.observable(self.observable) def toRxLater: RxLater[A] = RxLater.wrap(self) } From 0e4fcfe6bc1e68b0c46cb5391c0f9995b2ad3bb5 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Wed, 23 Aug 2023 00:30:06 +0200 Subject: [PATCH 32/44] improvements --- README.md | 2 +- .../main/scala/colibri/reactive/Owner.scala | 2 +- .../scala/colibri/reactive/Reactive.scala | 21 ++++++++++++------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 73592674..d7f48bb6 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ println(variable2.now()) // "Foo" println(rx.now()) // "3 - Foo" ``` -Apart from `Rx` which always has an initial value, there is `RxLater` which will eventually have a value (both extend RxState which extends RxSource). It also meant for representing state just without an initial state. It is lazy, distinct and has shared execution just like `Rx`. +Apart from `Rx` which always has an initial value, there is `RxLater` (and `VarLater`) which will eventually have a value (both extend RxState which extends RxSource). It also meant for representing state just without an initial state. It is lazy, distinct and has shared execution just like `Rx`. ``` import colibri.reactive._ diff --git a/reactive/src/main/scala/colibri/reactive/Owner.scala b/reactive/src/main/scala/colibri/reactive/Owner.scala index cd9c499a..1973055d 100644 --- a/reactive/src/main/scala/colibri/reactive/Owner.scala +++ b/reactive/src/main/scala/colibri/reactive/Owner.scala @@ -41,7 +41,7 @@ object LiveOwner extends LiveOwnerPlatform { } def unsafeLive[A](rx: Rx[A]): A = { - ref.unsafeAdd(() => rx.observable.via(subject).unsafeSubscribe()) + ref.unsafeAdd(() => rx.observable.unsafeSubscribe(subject)) rx.nowIfSubscribed() } } diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index a7804fa1..edd4cbde 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -108,9 +108,9 @@ trait RxEvent[+A] extends RxSource[A] with RxSourceSelf[RxEvent, RxEvent, A] { object RxEvent extends RxPlatform { private val _empty: RxEvent[Nothing] = observableUnshared(Observable.empty) - def empty[A]: RxEvent[A] = _empty + @inline def empty[A]: RxEvent[A] = _empty - def apply[A](values: A*): RxEvent[A] = iterable(values) + @inline def apply[A](values: A*): RxEvent[A] = iterable(values) def iterable[A](values: Iterable[A]): RxEvent[A] = observableUnshared(Observable.fromIterable(values)) @@ -121,8 +121,8 @@ object RxEvent extends RxPlatform { def switch[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.switchIterable(rxs.map(_.observable))) def concat[A](rxs: RxEvent[A]*): RxEvent[A] = observableUnshared(Observable.concatIterable(rxs.map(_.observable))) - def observable[A](observable: Observable[A]): RxEvent[A] = observableUnshared(observable.publish.refCount) - private def observableUnshared[A](observable: Observable[A]): RxEvent[A] = new RxEventObservable(observable) + def observable[A](observable: Observable[A]): RxEvent[A] = new RxEventObservableShared(observable) + private def observableUnshared[A](observable: Observable[A]): RxEvent[A] = new RxEventObservableUnshared(observable) @inline implicit final class RxEventOps[A](private val self: RxEvent[A]) extends AnyVal { def toRxLater: RxLater[A] = RxLater.observable(self.observable) @@ -144,14 +144,14 @@ trait RxLater[+A] extends RxState[A] with RxSourceSelf[RxLater, RxLater, A] { } object RxLater { - def empty[A]: RxLater[A] = RxLaterEmpty + @inline def empty[A]: RxLater[A] = RxLaterEmpty def future[A](future: => Future[A]): RxLater[A] = observable(Observable.fromFuture(future)) def effect[F[_]: RunEffect, A](effect: F[A]): RxLater[A] = observable(Observable.fromEffect(effect)) def observable[A](observable: Observable[A]): RxLater[A] = new RxLaterObservable(observable) - def wrap[A](rx: Rx[A]): RxLater[A] = new RxLaterWrap(rx) + def rx[A](rx: Rx[A]): RxLater[A] = new RxLaterWrap(rx) @inline implicit final class RxLaterOps[A](private val self: RxLater[A]) extends AnyVal { def toRxEvent: RxEvent[A] = RxEvent.observable(self.observable) @@ -166,6 +166,7 @@ trait Rx[+A] extends RxState[A] with RxSourceSelf[RxLater, Rx, A] { def apply()(implicit owner: LiveOwner): A def now()(implicit owner: NowOwner): A + def nowIfSubscribedOption(): Option[A] final def nowIfSubscribed(): A = nowIfSubscribedOption().getOrElse(throw RxMissingNowException) @@ -207,7 +208,7 @@ object Rx extends RxPlatform { @inline implicit final class RxLaterOps[A](private val self: Rx[A]) extends AnyVal { def toRxEvent: RxEvent[A] = RxEvent.observable(self.observable) - def toRxLater: RxLater[A] = RxLater.wrap(self) + def toRxLater: RxLater[A] = RxLater.rx(self) } } @@ -381,7 +382,11 @@ object Var { // RxEvent -private final class RxEventObservable[A](val observable: Observable[A]) extends RxEvent[A] +private final class RxEventObservableShared[A](inner: Observable[A]) extends RxEvent[A] { + val observable: Observable[A] = inner.publish.refCount +} + +private final class RxEventObservableUnshared[A](val observable: Observable[A]) extends RxEvent[A] // Rx From c8f667be4d9ef4ad8fdf69b3304e5b32c13158c1 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Wed, 20 Sep 2023 10:39:12 +0200 Subject: [PATCH 33/44] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d7f48bb6..f2366ae5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/cornerman/colibri.svg?branch=master)](https://travis-ci.org/cornerman/colibri) + [![Build Status](https://travis-ci.org/cornerman/colibri.svg?branch=master)](https://travis-ci.org/cornerman/colibri) # Colibri @@ -21,7 +21,7 @@ libraryDependencies += "com.github.cornerman" %%% "colibri" % "0.8.0" import colibri._ ``` -Reactive variables with lazy, distinct, shared state variables (a bit like scala-rx): +Reactive variables with lazy, distinct, shared state variables (a bit like scala-rx, but lazy): ```scala libraryDependencies += "com.github.cornerman" %%% "colibri-reactive" % "0.8.0" ``` From 809e29c6b1f310c52e69b3f73a45475b47a50ae4 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 6 Nov 2023 20:52:38 +0100 Subject: [PATCH 34/44] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2366ae5..e2e97f04 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - [![Build Status](https://travis-ci.org/cornerman/colibri.svg?branch=master)](https://travis-ci.org/cornerman/colibri) +[![Build Status](https://travis-ci.org/cornerman/colibri.svg?branch=master)](https://travis-ci.org/cornerman/colibri) # Colibri From 453eb88e17951d5834fc2892459da50a6adec6cb Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 6 Nov 2023 21:20:25 +0100 Subject: [PATCH 35/44] add void --- reactive/src/main/scala/colibri/reactive/Reactive.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index edd4cbde..7e4af35a 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -36,6 +36,7 @@ trait RxSourceSelf[+Self[+X] <: RxSource[X], +SelfSync[+X] <: RxSource[X], +A] { final def map[B](f: A => B): SelfSync[B] = transformRxSync(_.map(f)) final def tap(f: A => Unit): SelfSync[A] = transformRxSync(_.tap(f)) + final def void: SelfSync[Unit] = map(_ => ()) final def collect[B](f: PartialFunction[A, B]): Self[B] = transformRx(_.collect(f)) final def mapEither[B](f: A => Either[Throwable, B]): Self[B] = transformRx(_.mapEither(f)) From 29351ce7c02405e86e625399ef116f1f32a90b1d Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Mon, 6 Nov 2023 21:58:11 +0100 Subject: [PATCH 36/44] undeprecate to again --- colibri/src/main/scala/colibri/Observable.scala | 1 - reactive/src/main/scala/colibri/reactive/Reactive.scala | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/colibri/src/main/scala/colibri/Observable.scala b/colibri/src/main/scala/colibri/Observable.scala index 2dbea529..45242b39 100644 --- a/colibri/src/main/scala/colibri/Observable.scala +++ b/colibri/src/main/scala/colibri/Observable.scala @@ -432,7 +432,6 @@ object Observable { def unsafeSubscribe(sink2: Observer[A]): Cancelable = source.unsafeSubscribe(Observer.combine(sink, sink2)) } - @deprecated("Use via instead", "0.7.8") def to(sink: Observer[A]): Observable[Unit] = via(sink).void @deprecated("Use tap instead", "0.7.8") diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 7e4af35a..a006f93f 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -36,7 +36,7 @@ trait RxSourceSelf[+Self[+X] <: RxSource[X], +SelfSync[+X] <: RxSource[X], +A] { final def map[B](f: A => B): SelfSync[B] = transformRxSync(_.map(f)) final def tap(f: A => Unit): SelfSync[A] = transformRxSync(_.tap(f)) - final def void: SelfSync[Unit] = map(_ => ()) + final def void: SelfSync[Unit] = map(_ => ()) final def collect[B](f: PartialFunction[A, B]): Self[B] = transformRx(_.collect(f)) final def mapEither[B](f: A => Either[Throwable, B]): Self[B] = transformRx(_.mapEither(f)) @@ -53,7 +53,8 @@ trait RxSourceSelf[+Self[+X] <: RxSource[X], +SelfSync[+X] <: RxSource[X], +A] { final def asEffect[F[_]: RunEffect, B](value: F[B]): Self[B] = transformRx(_.asEffect(value)) final def asFuture[B](value: => Future[B]): Self[B] = transformRx(_.asFuture(value)) - final def via(writer: RxWriter[A]): SelfSync[A] = transformRxSync(_.via(writer.observer)) + final def via(writer: RxWriter[A]): SelfSync[A] = transformRxSync(_.via(writer.observer)) + final def to(writer: RxWriter[A]): SelfSync[Unit] = transformRxSync(_.to(writer.observer)) final def switchMap[B](f: A => RxSource[B]): Self[B] = transformRx(_.switchMap(f andThen (_.observable))) final def mergeMap[B](f: A => RxSource[B]): Self[B] = transformRx(_.mergeMap(f andThen (_.observable))) From 4383662ee078d3807912f2d4e8496db56d0306ee Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Wed, 8 Nov 2023 01:16:30 +0100 Subject: [PATCH 37/44] fix Cancelable.Builder if it is cancelled while subscribing --- colibri/src/main/scala/colibri/Cancelable.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colibri/src/main/scala/colibri/Cancelable.scala b/colibri/src/main/scala/colibri/Cancelable.scala index adc9e074..977b8896 100644 --- a/colibri/src/main/scala/colibri/Cancelable.scala +++ b/colibri/src/main/scala/colibri/Cancelable.scala @@ -46,7 +46,8 @@ object Cancelable { def unsafeAdd(subscription: () => Cancelable): Unit = if (buffer != null) { val cancelable = subscription() - buffer.push(cancelable) + if (buffer == null) cancelable.unsafeCancel() + else buffer.push(cancelable) () } From e8454a49ed10330f50bade0595640fe24cbf27c4 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Wed, 8 Nov 2023 01:31:29 +0100 Subject: [PATCH 38/44] own subscription for now() in lens() --- .../src/main/scala/colibri/reactive/Reactive.scala | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index a006f93f..a7a41b52 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -281,9 +281,7 @@ object VarLater { } trait Var[A] extends VarState[A] with Rx[A] { - final def updateIfSubscribed(f: A => A): Unit = set(f(nowIfSubscribed())) - - final def update(f: PartialFunction[A, A]) = { + final def update(f: PartialFunction[A, A])(implicit owner: NowOwner) = { val value = this.now() this.set(f.applyOrElse(value, (_: A) => value)) } @@ -292,9 +290,9 @@ trait Var[A] extends VarState[A] with Rx[A] { final def transformVarRead(g: Rx[A] => Rx[A]): Var[A] = Var.createStateless(this, g(this)) final def transformVarWrite(f: RxWriter[A] => RxWriter[A]): Var[A] = Var.createStateless(f(this), this) - final def imap[A2](f: A2 => A)(g: A => A2): Var[A2] = transformVar(_.contramap(f))(_.map(g)) - final def lens[B](read: A => B)(write: (A, B) => A): Var[B] = - transformVar(_.contramap(write(nowIfSubscribed(), _)))(_.map(read)) + final def imap[A2](f: A2 => A)(g: A => A2): Var[A2] = transformVar(_.contramap(f))(_.map(g)) + final def lens[B](read: A => B)(write: (A, B) => A)(implicit owner: NowOwner): Var[B] = + transformVar(_.contramap(write(now(), _)))(_.map(read)) final def prism[A2](f: A2 => A)(g: A => Option[A2])(seed: => A2): Var[A2] = transformVar(_.contramap(f))(rx => Rx.observableSync(rx.observable.mapFilter(g).prependEval(seed))) From de020478104d0e2f125b9a8299e67da9698f55dd Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Wed, 8 Nov 2023 01:54:30 +0100 Subject: [PATCH 39/44] fix --- reactive/src/main/scala/colibri/reactive/Reactive.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index a7a41b52..72397ba6 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -262,7 +262,7 @@ object VarEvent { def create[A](write: RxWriter[A], read: RxEvent[A]): VarEvent[A] = new VarEventCreate(write, read) } -trait VarState[A] extends RxWriter[A] with RxState[A] +trait VarState[A] extends VarSource[A] with RxState[A] trait VarLater[A] extends VarState[A] with RxLater[A] { final def transformVar[A2](f: RxWriter[A] => RxWriter[A2])(g: RxLater[A] => RxLater[A2]): VarLater[A2] = From 6021446881152a77bac22813c4613665a563dc8b Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Wed, 8 Nov 2023 17:13:33 +0100 Subject: [PATCH 40/44] minor refactoring --- reactive/src/main/scala/colibri/reactive/Reactive.scala | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 72397ba6..5b9284f5 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -189,12 +189,9 @@ object Rx extends RxPlatform { .subscribing(owner.liveObservable.dropSyncAll.head.via(subject)) .tapCancel(owner.cancelable.unsafeCancel) } catch { - case NonFatal(t) => - owner.cancelable.unsafeCancel() - Observable.raiseError(t) - case t: Throwable => - owner.cancelable.unsafeCancel() - throw t + case NonFatal(t) => Observable.raiseError(t) + } finally { + owner.cancelable.unsafeCancel() } } From 0c385e86d5abf7cf2b7fb4ad5f08aacd7e0653f8 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Fri, 10 Nov 2023 14:05:02 +0100 Subject: [PATCH 41/44] Revert "minor refactoring" This reverts commit 6021446881152a77bac22813c4613665a563dc8b. --- reactive/src/main/scala/colibri/reactive/Reactive.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/reactive/src/main/scala/colibri/reactive/Reactive.scala b/reactive/src/main/scala/colibri/reactive/Reactive.scala index 5b9284f5..72397ba6 100644 --- a/reactive/src/main/scala/colibri/reactive/Reactive.scala +++ b/reactive/src/main/scala/colibri/reactive/Reactive.scala @@ -189,9 +189,12 @@ object Rx extends RxPlatform { .subscribing(owner.liveObservable.dropSyncAll.head.via(subject)) .tapCancel(owner.cancelable.unsafeCancel) } catch { - case NonFatal(t) => Observable.raiseError(t) - } finally { - owner.cancelable.unsafeCancel() + case NonFatal(t) => + owner.cancelable.unsafeCancel() + Observable.raiseError(t) + case t: Throwable => + owner.cancelable.unsafeCancel() + throw t } } From e677b91e6e66415a1ddb2b292357761f9ba53088 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Wed, 7 Dec 2022 12:56:36 +0100 Subject: [PATCH 42/44] remove observables types for Hot/Value/MaybeValue --- .../src/main/scala/colibri/Connectable.scala | 48 +++--------- .../src/main/scala/colibri/Observable.scala | 73 +++---------------- colibri/src/main/scala/colibri/Observer.scala | 4 +- colibri/src/main/scala/colibri/Subject.scala | 14 +--- 4 files changed, 21 insertions(+), 118 deletions(-) diff --git a/colibri/src/main/scala/colibri/Connectable.scala b/colibri/src/main/scala/colibri/Connectable.scala index e8a2f1f4..03859e4f 100644 --- a/colibri/src/main/scala/colibri/Connectable.scala +++ b/colibri/src/main/scala/colibri/Connectable.scala @@ -1,55 +1,25 @@ package colibri -final class Connectable[+T] private (val value: T, val connect: () => Cancelable) { - def map[A](f: T => A): Connectable[A] = new Connectable(f(value), connect) +final class Connectable[+T] private (val value: T, val unsafeConnect: () => Cancelable) { + def map[A](f: T => A): Connectable[A] = new Connectable(f(value), unsafeConnect) def flatMap[A](f: T => Connectable[A]): Connectable[A] = { val connectable = f(value) - new Connectable(connectable.value, () => Cancelable.composite(connect(), connectable.connect())) + new Connectable(connectable.value, () => Cancelable.composite(unsafeConnect(), connectable.unsafeConnect())) } } object Connectable { - def apply[T](value: T, connect: () => Cancelable) = { - val cancelable = Cancelable.refCount(connect) + def apply[T](value: T, unsafeConnect: () => Cancelable) = { + val cancelable = Cancelable.refCount(unsafeConnect) new Connectable(value, cancelable.ref) } @inline implicit class ConnectableObservableOperations[A](val source: Connectable[Observable[A]]) extends AnyVal { def refCount: Observable[A] = new Observable[A] { - def unsafeSubscribe(sink: Observer[A]): Cancelable = Cancelable.composite(source.value.unsafeSubscribe(sink), source.connect()) + def unsafeSubscribe(sink: Observer[A]): Cancelable = Cancelable.composite(source.value.unsafeSubscribe(sink), source.unsafeConnect()) } - @deprecated("Use unsafeHot instead", "0.5.0") - def hot: Observable.Hot[A] = unsafeHot() - def unsafeHot(): Observable.Hot[A] = new Observable.Hot[A] { - val cancelable = source.connect() - def unsafeSubscribe(sink: Observer[A]): Cancelable = source.value.unsafeSubscribe(sink) - } - } - - @inline implicit class ConnectableObservableValueOperations[A](val source: Connectable[Observable.Value[A]]) extends AnyVal { - def refCount: Observable.Value[A] = new Observable.Value[A] { - def now() = source.value.now() - def unsafeSubscribe(sink: Observer[A]): Cancelable = Cancelable.composite(source.value.unsafeSubscribe(sink), source.connect()) - } - @deprecated("Use unsafeHot instead", "0.7.8") - def hot: Observable.HotValue[A] = unsafeHot() - def unsafeHot(): Observable.HotValue[A] = new Observable.HotValue[A] { - val cancelable = source.connect() - def now() = source.value.now() - def unsafeSubscribe(sink: Observer[A]): Cancelable = source.value.unsafeSubscribe(sink) - } - } - - @inline implicit class ConnectableObservableMaybeValueOperations[A](val source: Connectable[Observable.MaybeValue[A]]) extends AnyVal { - def refCount: Observable.MaybeValue[A] = new Observable.MaybeValue[A] { - def now() = source.value.now() - def unsafeSubscribe(sink: Observer[A]): Cancelable = Cancelable.composite(source.value.unsafeSubscribe(sink), source.connect()) - } - @deprecated("Use unsafeHot instead", "0.7.8") - def hot: Observable.HotMaybeValue[A] = unsafeHot() - def unsafeHot(): Observable.HotMaybeValue[A] = new Observable.HotMaybeValue[A] { - val cancelable = source.connect() - def now() = source.value.now() - def unsafeSubscribe(sink: Observer[A]): Cancelable = source.value.unsafeSubscribe(sink) + def unsafeHot(): Observable[A] = { + val _ = source.unsafeConnect() + source.value } } } diff --git a/colibri/src/main/scala/colibri/Observable.scala b/colibri/src/main/scala/colibri/Observable.scala index 45242b39..8dd20a11 100644 --- a/colibri/src/main/scala/colibri/Observable.scala +++ b/colibri/src/main/scala/colibri/Observable.scala @@ -85,21 +85,6 @@ object Observable { def flatMap[B](f: A => Observable[B]): Observable[B] } - trait Value[+A] extends Observable[A] { - def now(): A - } - trait MaybeValue[+A] extends Observable[A] { - def now(): Option[A] - } - - trait HasCancelable { - def cancelable: Cancelable - } - - trait Hot[+A] extends Observable[A] with HasCancelable - trait HotValue[+A] extends Value[A] with HasCancelable - trait HotMaybeValue[+A] extends MaybeValue[A] with HasCancelable - object Empty extends Observable[Nothing] { @inline def unsafeSubscribe(sink: Observer[Nothing]): Cancelable = Cancelable.empty } @@ -1401,25 +1386,21 @@ object Observable { } @inline def publish: Connectable[Observable[A]] = multicast(Subject.publish[A]()) - @deprecated("Use replayLatest instead", "0.3.4") - @inline def replay: Connectable[Observable.MaybeValue[A]] = replayLatest - @inline def replayLatest: Connectable[Observable.MaybeValue[A]] = multicastMaybeValue(Subject.replayLatest[A]()) + @inline def replayLatest: Connectable[Observable[A]] = multicast(Subject.replayLatest[A]()) @inline def replayAll: Connectable[Observable[A]] = multicast(Subject.replayAll[A]()) - @inline def behavior(seed: A): Connectable[Observable.Value[A]] = multicastValue(Subject.behavior(seed)) + @inline def behavior(seed: A): Connectable[Observable[A]] = multicast(Subject.behavior(seed)) @inline def publishShare: Observable[A] = publish.refCount - @inline def replayLatestShare: Observable.MaybeValue[A] = replayLatest.refCount + @inline def replayLatestShare: Observable[A] = replayLatest.refCount @inline def replayAllShare: Observable[A] = replayAll.refCount - @inline def behaviorShare(seed: A): Observable.Value[A] = behavior(seed).refCount + @inline def behaviorShare(seed: A): Observable[A] = behavior(seed).refCount @inline def publishSelector[B](f: Observable[A] => Observable[B]): Observable[B] = transformSource(s => f(s.publish.refCount)) - @deprecated("Use replayLatestSelector instead", "0.3.4") - @inline def replaySelector[B](f: Observable.MaybeValue[A] => Observable[B]): Observable[B] = replayLatestSelector(f) - @inline def replayLatestSelector[B](f: Observable.MaybeValue[A] => Observable[B]): Observable[B] = + @inline def replayLatestSelector[B](f: Observable[A] => Observable[B]): Observable[B] = transformSource(s => f(s.replayLatest.refCount)) @inline def replayAllSelector[B](f: Observable[A] => Observable[B]): Observable[B] = transformSource(s => f(s.replayAll.refCount)) - @inline def behaviorSelector[B](value: A)(f: Observable.Value[A] => Observable[B]): Observable[B] = + @inline def behaviorSelector[B](value: A)(f: Observable[A] => Observable[B]): Observable[B] = transformSource(s => f(s.behavior(value).refCount)) def multicast(pipe: Subject[A]): Connectable[Observable[A]] = Connectable( @@ -1429,22 +1410,6 @@ object Observable { () => source.unsafeSubscribe(pipe), ) - def multicastValue(pipe: Subject.Value[A]): Connectable[Observable.Value[A]] = Connectable( - new Value[A] { - def now(): A = pipe.now() - def unsafeSubscribe(sink: Observer[A]): Cancelable = pipe.unsafeSubscribe(sink) - }, - () => source.unsafeSubscribe(pipe), - ) - - def multicastMaybeValue(pipe: Subject.MaybeValue[A]): Connectable[Observable.MaybeValue[A]] = Connectable( - new MaybeValue[A] { - def now(): Option[A] = pipe.now() - def unsafeSubscribe(sink: Observer[A]): Cancelable = pipe.unsafeSubscribe(sink) - }, - () => source.unsafeSubscribe(pipe), - ) - def fold[B](seed: B)(f: (B, A) => B): Observable[B] = scan(seed)(f).last def foldF[F[_]: Async, B](seed: B)(f: (B, A) => B): F[B] = scan(seed)(f).lastF[F] def foldIO[B](seed: B)(f: (B, A) => B): IO[B] = scan(seed)(f).lastIO @@ -1810,24 +1775,6 @@ object Observable { @inline def flattenSwitch: Observable[A] = source.switchMap(o => ObservableLike[F].toObservable(o)) } - @inline implicit class SubjectValueOperations[A](val handler: Subject.Value[A]) extends AnyVal { - def lens[B](read: A => B)(write: (A, B) => A): Subject.Value[B] = new Observer[B] with Observable.Value[B] { - @inline def now() = read(handler.now()) - @inline def unsafeOnNext(value: B): Unit = handler.unsafeOnNext(write(handler.now(), value)) - @inline def unsafeOnError(error: Throwable): Unit = handler.unsafeOnError(error) - @inline def unsafeSubscribe(sink: Observer[B]): Cancelable = handler.map(read).unsafeSubscribe(sink) - } - } - - @inline implicit class SubjectMaybeValueOperations[A](val handler: Subject.MaybeValue[A]) extends AnyVal { - def lens[B](seed: => A)(read: A => B)(write: (A, B) => A): Subject.MaybeValue[B] = new Observer[B] with Observable.MaybeValue[B] { - @inline def now() = handler.now().map(read) - @inline def unsafeOnNext(value: B): Unit = handler.unsafeOnNext(write(handler.now().getOrElse(seed), value)) - @inline def unsafeOnError(error: Throwable): Unit = handler.unsafeOnError(error) - @inline def unsafeSubscribe(sink: Observer[B]): Cancelable = handler.map(read).unsafeSubscribe(sink) - } - } - @inline implicit class ProSubjectOperations[I, O](val handler: ProSubject[I, O]) extends AnyVal { @inline def transformSubjectSource[O2](g: Observable[O] => Observable[O2]): ProSubject[I, O2] = ProSubject.from[I, O2](handler, g(handler)) @@ -1844,15 +1791,13 @@ object Observable { } @inline implicit class ListSubjectOperations[A](val handler: Subject[Seq[A]]) extends AnyVal { - def sequence: Observable[Seq[Subject.Value[A]]] = new Observable[Seq[Subject.Value[A]]] { - def unsafeSubscribe(sink: Observer[Seq[Subject.Value[A]]]): Cancelable = { + def sequence: Observable[Seq[Subject[A]]] = new Observable[Seq[Subject[A]]] { + def unsafeSubscribe(sink: Observer[Seq[Subject[A]]]): Cancelable = { handler.unsafeSubscribe( Observer.create( { sequence => sink.unsafeOnNext(sequence.zipWithIndex.map { case (a, idx) => - new Observer[A] with Observable.Value[A] { - def now(): A = a - + new Observer[A] with Observable[A] { def unsafeSubscribe(sink: Observer[A]): Cancelable = { sink.unsafeOnNext(a) Cancelable.empty diff --git a/colibri/src/main/scala/colibri/Observer.scala b/colibri/src/main/scala/colibri/Observer.scala index daa6ca40..323ab5ad 100644 --- a/colibri/src/main/scala/colibri/Observer.scala +++ b/colibri/src/main/scala/colibri/Observer.scala @@ -90,8 +90,6 @@ object Observer { @inline def combine[A](sinks: Observer[A]*): Observer[A] = combineIterable(sinks) - @deprecated("Use combineIterable instead", "0.5.0") - def combineSeq[A](sinks: Seq[Observer[A]]): Observer[A] = combineIterable(sinks) def combineIterable[A](sinks: Iterable[Observer[A]]): Observer[A] = new Observer[A] { def unsafeOnNext(value: A): Unit = sinks.foreach(_.unsafeOnNext(value)) def unsafeOnError(error: Throwable): Unit = sinks.foreach(_.unsafeOnError(error)) @@ -232,7 +230,7 @@ object Observer { } @inline implicit class UnitOperations(private val sink: Observer[Unit]) extends AnyVal { - @inline def void: Observer[Any] = sink.contramap(_ => ()) + @inline def void: Observer[Any] = sink.as(()) } @inline implicit class ThrowableOperations(private val sink: Observer[Throwable]) extends AnyVal { diff --git a/colibri/src/main/scala/colibri/Subject.scala b/colibri/src/main/scala/colibri/Subject.scala index 330ce243..5f67d4d6 100644 --- a/colibri/src/main/scala/colibri/Subject.scala +++ b/colibri/src/main/scala/colibri/Subject.scala @@ -3,7 +3,7 @@ package colibri import scala.scalajs.js import colibri.helpers._ -final class ReplayLatestSubject[A] extends Observer[A] with Observable.MaybeValue[A] { +final class ReplayLatestSubject[A] extends Observer[A] with Observable[A] { private val state = new PublishSubject[A] @@ -59,7 +59,7 @@ final class ReplayAllSubject[A] extends Observer[A] with Observable[A] { } } -final class BehaviorSubject[A](private var current: A) extends Observer[A] with Observable.Value[A] { +final class BehaviorSubject[A](private var current: A) extends Observer[A] with Observable[A] { private val state = new PublishSubject[A] @@ -111,13 +111,6 @@ final class PublishSubject[A] extends Observer[A] with Observable[A] { } object Subject { - type Value[A] = Observer[A] with Observable.Value[A] - type MaybeValue[A] = Observer[A] with Observable.MaybeValue[A] - - @deprecated("Use replayLatest instead", "0.3.4") - def replay[O](): ReplayLatestSubject[O] = replayLatest[O]() - @deprecated("Use replayLatest instead", "0.4.0") - def replayLast[O](): ReplayLatestSubject[O] = replayLatest[O]() def replayLatest[O](): ReplayLatestSubject[O] = new ReplayLatestSubject[O] def replayAll[O](): ReplayAllSubject[O] = new ReplayAllSubject[O] @@ -131,9 +124,6 @@ object Subject { } object ProSubject { - type Value[-I, +O] = Observer[I] with Observable.Value[O] - type MaybeValue[-I, +O] = Observer[I] with Observable.MaybeValue[O] - def from[I, O](sink: Observer[I], source: Observable[O]): ProSubject[I, O] = new Observer[I] with Observable[O] { @inline def unsafeOnNext(value: I): Unit = sink.unsafeOnNext(value) @inline def unsafeOnError(error: Throwable): Unit = sink.unsafeOnError(error) From 5d52d9de617dfdd3ea3d3bc4fbf636a15590a286 Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Wed, 7 Dec 2022 13:01:01 +0100 Subject: [PATCH 43/44] remove overdue deprecations --- .../src/main/scala/colibri/CanCancel.scala | 3 - .../src/main/scala/colibri/Cancelable.scala | 3 - .../src/main/scala/colibri/Observable.scala | 70 +------------------ colibri/src/main/scala/colibri/Observer.scala | 10 --- colibri/src/main/scala/colibri/Source.scala | 3 - 5 files changed, 1 insertion(+), 88 deletions(-) diff --git a/colibri/src/main/scala/colibri/CanCancel.scala b/colibri/src/main/scala/colibri/CanCancel.scala index 4b8ab9a7..dc39a28f 100644 --- a/colibri/src/main/scala/colibri/CanCancel.scala +++ b/colibri/src/main/scala/colibri/CanCancel.scala @@ -2,9 +2,6 @@ package colibri trait CanCancel[-T] { def unsafeCancel(cancelable: T): Unit - - @deprecated("Use unsafeCancel instead", "0.2.7") - @inline final def cancel(cancelable: T): Unit = unsafeCancel(cancelable) } object CanCancel { @inline def apply[T](implicit unsafeCancel: CanCancel[T]): CanCancel[T] = unsafeCancel diff --git a/colibri/src/main/scala/colibri/Cancelable.scala b/colibri/src/main/scala/colibri/Cancelable.scala index 977b8896..1979752e 100644 --- a/colibri/src/main/scala/colibri/Cancelable.scala +++ b/colibri/src/main/scala/colibri/Cancelable.scala @@ -10,9 +10,6 @@ trait Cancelable { def isEmpty(): Boolean def unsafeCancel(): Unit - @deprecated("Use unsafeCancel() instead", "0.2.7") - @inline final def cancel(): Unit = unsafeCancel() - final def cancelF[F[_]: Sync]: F[Unit] = Sync[F].delay(unsafeCancel()) final def cancelIO: IO[Unit] = cancelF[IO] final def cancelSyncIO: SyncIO[Unit] = cancelF[SyncIO] diff --git a/colibri/src/main/scala/colibri/Observable.scala b/colibri/src/main/scala/colibri/Observable.scala index 8dd20a11..762fcaab 100644 --- a/colibri/src/main/scala/colibri/Observable.scala +++ b/colibri/src/main/scala/colibri/Observable.scala @@ -2,7 +2,7 @@ package colibri import cats._ import cats.implicits._ -import colibri.effect.{RunSyncEffect, RunEffect} +import colibri.effect.RunEffect import cats.effect.{Sync, SyncIO, Async, IO, Resource} import scala.scalajs.js @@ -114,8 +114,6 @@ object Observable { def unsafeSubscribe(sink: Observer[T]): Cancelable = value.unsafeSubscribe(sink) } - @deprecated("Use Observable.raiseError instead", "0.3.0") - def failure[T](error: Throwable): Observable[T] = raiseError(error) def raiseError[T](error: Throwable): Observable[T] = new Observable[T] { def unsafeSubscribe(sink: Observer[T]): Cancelable = { sink.unsafeOnError(error) @@ -158,10 +156,6 @@ object Observable { } } - @deprecated("Use fromEffect instead", "0.3.0") - def fromSync[F[_]: RunSyncEffect, A](effect: F[A]): Observable[A] = fromEffect(effect) - @deprecated("Use fromEffect instead", "0.3.0") - def fromAsync[F[_]: RunEffect, A](effect: F[A]): Observable[A] = fromEffect(effect) def fromEffect[F[_]: RunEffect, A](effect: F[A]): Observable[A] = new Observable[A] { def unsafeSubscribe(sink: Observer[A]): Cancelable = RunEffect[F].unsafeRunSyncOrAsyncCancelable[A](effect)(_.fold(sink.unsafeOnError, sink.unsafeOnNext)) @@ -197,11 +191,6 @@ object Observable { def like[H[_]: ObservableLike, A](observableLike: H[A]): Observable[A] = ObservableLike[H].toObservable(observableLike) - @deprecated("Use concatEffect instead", "0.3.0") - def concatSync[F[_]: RunSyncEffect, T](effects: F[T]*): Observable[T] = concatEffect(effects: _*) - @deprecated("Use concatEffect instead", "0.3.0") - def concatAsync[F[_]: RunEffect, T](effects: F[T]*): Observable[T] = concatEffect(effects: _*) - def concatEffect[F[_]: RunEffect, T](effects: F[T]*): Observable[T] = fromIterable(effects).mapEffect(identity) def concatFuture[T](value1: => Future[T]): Observable[T] = concatEffect(IO.fromFuture(IO(value1))) @@ -225,10 +214,6 @@ object Observable { IO.fromFuture(IO(value5)), ) - @deprecated("Use concatEffect instead", "0.3.0") - def concatSync[F[_]: RunSyncEffect, T](effect: F[T], source: Observable[T]): Observable[T] = concatEffect(effect, source) - @deprecated("Use concatEffect instead", "0.3.0") - def concatAsync[F[_]: RunEffect, T](effect: F[T], source: Observable[T]): Observable[T] = concatEffect(effect, source) def concatEffect[F[_]: RunEffect, T](effect: F[T], source: Observable[T]): Observable[T] = new Observable[T] { def unsafeSubscribe(sink: Observer[T]): Cancelable = { val consecutive = Cancelable.consecutive() @@ -248,8 +233,6 @@ object Observable { @inline def merge[A](sources: Observable[A]*): Observable[A] = mergeIterable(sources) - @deprecated("Use mergeIterable instead", "0.4.5") - def mergeSeq[A](sources: Seq[Observable[A]]): Observable[A] = mergeIterable(sources) def mergeIterable[A](sources: Iterable[Observable[A]]): Observable[A] = new Observable[A] { def unsafeSubscribe(sink: Observer[A]): Cancelable = { val subscriptions = sources.map { source => @@ -262,8 +245,6 @@ object Observable { @inline def switch[A](sources: Observable[A]*): Observable[A] = switchIterable(sources) - @deprecated("Use switchIterable instead", "0.4.5") - def switchSeq[A](sources: Seq[Observable[A]]): Observable[A] = switchIterable(sources) def switchIterable[A](sources: Iterable[Observable[A]]): Observable[A] = new Observable[A] { def unsafeSubscribe(sink: Observer[A]): Cancelable = { val variable = Cancelable.variable() @@ -278,8 +259,6 @@ object Observable { @inline def concat[A](sources: Observable[A]*): Observable[A] = concatIterable(sources) - @deprecated("Use concatIterable instead", "0.4.5") - def concatSeq[A](sources: Seq[Observable[A]]): Observable[A] = concatIterable(sources) def concatIterable[A](sources: Iterable[Observable[A]]): Observable[A] = new Observable[A] { def unsafeSubscribe(sink: Observer[A]): Cancelable = { val consecutive = Cancelable.consecutive() @@ -549,9 +528,6 @@ object Observable { source.unsafeSubscribe(Observer.createFromEither(sink.unsafeOnNext)) } - @deprecated("Use attempt instead", "0.3.0") - def recoverToEither: Observable[Either[Throwable, A]] = source.attempt - def recoverMap(f: Throwable => A): Observable[A] = recover { case t => f(t) } def recover(f: PartialFunction[Throwable, A]): Observable[A] = recoverOption(f andThen (Some(_))) @@ -599,8 +575,6 @@ object Observable { def tapFailedEffect[F[_]: RunEffect: Applicative](f: Throwable => F[Unit]): Observable[A] = attempt.tapEffect(_.swap.traverseTap(f).void).flattenEither - @deprecated("Use .tapSubscribe(f) instead", "0.3.4") - def doOnSubscribe(f: () => Cancelable): Observable[A] = tapSubscribe(f) def tapSubscribe(f: () => Cancelable): Observable[A] = new Observable[A] { def unsafeSubscribe(sink: Observer[A]): Cancelable = { val cancelable = f() @@ -622,8 +596,6 @@ object Observable { } } - @deprecated("Use .tap(f) instead", "0.3.4") - def doOnNext(f: A => Unit): Observable[A] = tap(f) def tap(f: A => Unit): Observable[A] = new Observable[A] { def unsafeSubscribe(sink: Observer[A]): Cancelable = { source.unsafeSubscribe(sink.doOnNext { value => @@ -633,8 +605,6 @@ object Observable { } } - @deprecated("Use .tapFailed(f) instead", "0.3.4") - def doOnError(f: Throwable => Unit): Observable[A] = tapFailed(f) def tapFailed(f: Throwable => Unit): Observable[A] = new Observable[A] { def unsafeSubscribe(sink: Observer[A]): Cancelable = { source.unsafeSubscribe(sink.doOnError { error => @@ -681,11 +651,6 @@ object Observable { } } - @deprecated("Use mapEffect instead", "0.3.0") - @inline def mapSync[F[_]: RunSyncEffect, B](f: A => F[B]): Observable[B] = mapEffect(f) - @deprecated("Use mapEffect instead", "0.3.0") - def mapAsync[F[_]: RunEffect, B](f: A => F[B]): Observable[B] = mapEffect(f) - @inline def mapEffect[F[_]: RunEffect, B](f: A => F[B]): Observable[B] = concatMapEffect(f) def concatMapEffect[F[_]: RunEffect, B](f: A => F[B]): Observable[B] = new Observable[B] { def unsafeSubscribe(sink: Observer[B]): Cancelable = { @@ -768,10 +733,6 @@ object Observable { } } - @deprecated("Use singleMapEffect instead", "0.3.0") - def mapAsyncSingleOrDrop[F[_]: RunEffect, B](f: A => F[B]): Observable[B] = singleMapEffect(f) - @deprecated("Use singleMapEffect instead", "0.7.2") - def mapEffectSingleOrDrop[F[_]: RunEffect, B](f: A => F[B]): Observable[B] = singleMapEffect(f) def singleMapEffect[F[_]: RunEffect, B](f: A => F[B]): Observable[B] = new Observable[B] { def unsafeSubscribe(sink: Observer[B]): Cancelable = { val single = Cancelable.singleOrDrop() @@ -802,8 +763,6 @@ object Observable { } } - @deprecated("Use singleMapFuture instead", "0.7.2") - @inline def mapFutureSingleOrDrop[B](f: A => Future[B]): Observable[B] = singleMapFuture(f) @inline def singleMapFuture[B](f: A => Future[B]): Observable[B] = singleMapEffect(v => IO.fromFuture(IO(f(v)))) @@ -1362,21 +1321,6 @@ object Observable { @inline def distinctByOnEquals[B](f: A => B): Observable[A] = distinctBy(f)(Eq.fromUniversalEquals) @inline def distinctOnEquals: Observable[A] = distinct(Eq.fromUniversalEquals) - @deprecated("Manage subscriptions directly with subscribe, via, etc.", "0.4.3") - def withDefaultSubscription(sink: Observer[A]): Observable[A] = new Observable[A] { - private var defaultSubscription = source.unsafeSubscribe(sink) - - def unsafeSubscribe(sink: Observer[A]): Cancelable = { - // stop the default subscription. - if (defaultSubscription != null) { - defaultSubscription.unsafeCancel() - defaultSubscription = null - } - - source.unsafeSubscribe(sink) - } - } - @inline def transformSource[B](transform: Observable[A] => Observable[B]): Observable[B] = new Observable[B] { def unsafeSubscribe(sink: Observer[B]): Cancelable = transform(source).unsafeSubscribe(sink) } @@ -1415,11 +1359,6 @@ object Observable { def foldIO[B](seed: B)(f: (B, A) => B): IO[B] = scan(seed)(f).lastIO def unsafeFoldFuture[B](seed: B)(f: (B, A) => B): Future[B] = scan(seed)(f).unsafeLastFuture() - @deprecated("Use prependEffect instead", "0.3.0") - @inline def prependSync[F[_]: RunSyncEffect](value: F[A]): Observable[A] = prependEffect(value) - @deprecated("Use prependEffect instead", "0.3.0") - @inline def prependAsync[F[_]: RunEffect](value: F[A]): Observable[A] = prependEffect(value) - @inline def prependEffect[F[_]: RunEffect](value: F[A]): Observable[A] = concatEffect[F, A](value, source) @inline def prependFuture(value: => Future[A]): Observable[A] = concatFuture[A](value, source) @@ -1732,13 +1671,6 @@ object Observable { @inline def unsafeSubscribe(): Cancelable = source.unsafeSubscribe(Observer.empty) @inline def unsafeForeach(f: A => Unit): Cancelable = source.unsafeSubscribe(Observer.create(f)) - - @deprecated("Use unsafeSubscribe(sink) or to(sink).unsafeSubscribe() or to(sink).subscribeF[F] instead", "0.3.0") - @inline def subscribe(sink: Observer[A]): Cancelable = source.unsafeSubscribe(sink) - @deprecated("Use unsafeSubscribe() or subscribeF[F] instead", "0.3.0") - @inline def subscribe(): Cancelable = unsafeSubscribe() - @deprecated("Use unsafeForeach(f) or foreach_(f).subscribeF[F] instead", "0.3.0") - @inline def foreach(f: A => Unit): Cancelable = unsafeForeach(f) } @inline implicit class ThrowableOperations(private val source: Observable[Throwable]) extends AnyVal { diff --git a/colibri/src/main/scala/colibri/Observer.scala b/colibri/src/main/scala/colibri/Observer.scala index 323ab5ad..3ca0e24d 100644 --- a/colibri/src/main/scala/colibri/Observer.scala +++ b/colibri/src/main/scala/colibri/Observer.scala @@ -29,11 +29,6 @@ object Observer { } } - @deprecated("Use createUnrecovered instead", "") - @inline def unsafeCreate[A]( - consume: A => Unit, - failure: Throwable => Unit = UnhandledErrorReporter.errorSubject.unsafeOnNext, - ): Observer[A] = createUnrecovered(consume, failure) @inline def createUnrecovered[A]( consume: A => Unit, failure: Throwable => Unit = UnhandledErrorReporter.errorSubject.unsafeOnNext, @@ -215,11 +210,6 @@ object Observer { Connectable(handler, () => source.unsafeSubscribe(sink)) } - @deprecated("Use unsafeOnNext instead", "") - def onNext(value: A): Unit = sink.unsafeOnNext(value) - @deprecated("Use unsafeOnError instead", "") - def onError(error: Throwable): Unit = sink.unsafeOnError(error) - def onNextF[F[_]: Sync](value: A): F[Unit] = Sync[F].delay(sink.unsafeOnNext(value)) def onNextIO(value: A): IO[Unit] = onNextF[IO](value) def onNextSyncIO(value: A): SyncIO[Unit] = onNextF[SyncIO](value) diff --git a/colibri/src/main/scala/colibri/Source.scala b/colibri/src/main/scala/colibri/Source.scala index 13efd067..1e6c86ba 100644 --- a/colibri/src/main/scala/colibri/Source.scala +++ b/colibri/src/main/scala/colibri/Source.scala @@ -2,9 +2,6 @@ package colibri trait Source[-H[_]] { def unsafeSubscribe[A](source: H[A])(sink: Observer[A]): Cancelable - - @deprecated("Use unsafeSubscribe instead", "0.2.7") - @inline final def subscribe[A](source: H[A])(sink: Observer[A]): Cancelable = unsafeSubscribe(source)(sink) } object Source { @inline def apply[H[_]](implicit source: Source[H]): Source[H] = source From 6a0bd8584ec3c0361f707b7b63eeef9ea04c291a Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Fri, 10 Nov 2023 14:21:20 +0100 Subject: [PATCH 44/44] remove Observable.lens test because does not exist anymore --- .../test/scala/colibri/ObservableSpec.scala | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/colibri/src/test/scala/colibri/ObservableSpec.scala b/colibri/src/test/scala/colibri/ObservableSpec.scala index aca4ae23..9914a969 100644 --- a/colibri/src/test/scala/colibri/ObservableSpec.scala +++ b/colibri/src/test/scala/colibri/ObservableSpec.scala @@ -1824,52 +1824,4 @@ class ObservableSpec extends AsyncFlatSpec with Matchers { errors shouldBe 0 cancelable.isEmpty() shouldBe true } - - it should "lens" in { - case class Recipe(user: String, age: Int) - - var receivedString = List.empty[String] - var receivedRecipe = List.empty[Recipe] - var errors = 0 - - val hdlRecipe = Subject.behavior[Recipe](Recipe("hans", 12)) - - val hdlUser = hdlRecipe.lens[String](_.user)((state, newState) => state.copy(user = newState)) - - hdlRecipe.unsafeSubscribe( - Observer.create[Recipe]( - receivedRecipe ::= _, - _ => errors += 1, - ), - ) - - hdlUser.unsafeSubscribe( - Observer.create[String]( - receivedString ::= _, - _ => errors += 1, - ), - ) - - receivedString shouldBe List("hans") - receivedRecipe shouldBe List(Recipe("hans", 12)) - errors shouldBe 0 - - hdlRecipe.unsafeOnNext(Recipe("hans", 13)) - - receivedString shouldBe List("hans", "hans") - receivedRecipe shouldBe List(Recipe("hans", 13), Recipe("hans", 12)) - errors shouldBe 0 - - hdlRecipe.unsafeOnNext(Recipe("gisela", 14)) - - receivedString shouldBe List("gisela", "hans", "hans") - receivedRecipe shouldBe List(Recipe("gisela", 14), Recipe("hans", 13), Recipe("hans", 12)) - errors shouldBe 0 - - hdlUser.unsafeOnNext("dieter") - - receivedString shouldBe List("dieter", "gisela", "hans", "hans") - receivedRecipe shouldBe List(Recipe("dieter", 14), Recipe("gisela", 14), Recipe("hans", 13), Recipe("hans", 12)) - errors shouldBe 0 - } }