Skip to content

Commit

Permalink
update readme about reactive variables
Browse files Browse the repository at this point in the history
  • Loading branch information
cornerman committed Jan 10, 2023
1 parent 18d6318 commit 852ac4d
Showing 1 changed file with 32 additions and 49 deletions.
81 changes: 32 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -146,79 +146,62 @@ 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:

```scala

import colibri.reactive._

import colibri.owner.unsafeImplicits._ // dangerous. This never cancels subscriptions. See below!

val variable = Var(1)
val variable2 = Var("Test")

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

Expand All @@ -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)

Expand All @@ -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

Expand Down

0 comments on commit 852ac4d

Please sign in to comment.