From a09ccbed90df342877c89b5ec2da6e784926b06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Wed, 15 Nov 2023 15:26:43 +0100 Subject: [PATCH 01/33] Initial commit --- .../index.template.html | 17 ++++++++ .../src/main/scala/copl/ConversionTest.scala | 43 +++++++++++++++++++ .../src/main/scala/copl/RenderUtil.scala | 25 +++++++++++ build.sbt | 30 +++++++++++-- 4 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 Modules/Example ReactiveLenses/index.template.html create mode 100644 Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala create mode 100644 Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala diff --git a/Modules/Example ReactiveLenses/index.template.html b/Modules/Example ReactiveLenses/index.template.html new file mode 100644 index 000000000..eca439bcc --- /dev/null +++ b/Modules/Example ReactiveLenses/index.template.html @@ -0,0 +1,17 @@ + + + + + + ConversionTest + + +
+ +
+ + + + diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala new file mode 100644 index 000000000..19a3a4444 --- /dev/null +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -0,0 +1,43 @@ +package copl + +import geny.Generator.from +import org.scalajs.dom.html.{Div, Input, Paragraph} +import org.scalajs.dom.{UIEvent, document, window} +import rescala.default.* +import rescala.extra.Tags.* +import scalatags.JsDom +import scalatags.JsDom.all.* +import scalatags.JsDom.{Attr, TypedTag} + +object ConversionTest { + + def main(args: Array[String]): Unit = { + val oneWayConverter = getOneWayConverter() + document.body.replaceChild(oneWayConverter.render, document.body.firstChild) + } + + def getOneWayConverter() = { + + val meterInput: TypedTag[Input] = input(placeholder := "Meters") + val (meterEvent: Event[String], renderedMeter: Input) = RenderUtil.inputFieldHandler(meterInput, oninput, clear = false) + + //Q: Used to be latest + val yardSignal: Signal[Option[Double]] = meterEvent.hold(init="Please enter a valid value for meters.").map { str => convertMeterToYard(str.toDoubleOption)} + + val yardParagraph: Signal[TypedTag[Paragraph]] = yardSignal.map { yard => p(if (yard.isEmpty) "Please enter a valid value for meters." else yard.get.toString)} + + div(p("One way conversion using Signal"), renderedMeter, yardParagraph.asModifier) + } + + def convertMeterToYard(meter : Option[Double]): Option[Double] = { + if(meter.isEmpty) + Option.empty[Double] + else + Option[Double](meter.get * 0.9144) + } + +} + +//Q: RenderUtil not working +//Q: LensBundle in rescala.operator +//Q: Integration of TopoBundle diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala new file mode 100644 index 000000000..0fbf7ed55 --- /dev/null +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala @@ -0,0 +1,25 @@ +package copl + +import org.scalajs.dom.UIEvent +import org.scalajs.dom.html.Input +import rescala.default._ +import scalatags.JsDom.all.* +import scalatags.JsDom.{Attr, TypedTag} + +object RenderUtil { + def inputFieldHandler(tag: TypedTag[Input], attr: Attr, clear: Boolean = true): (Event[String], Input) = { + val handler = Events.fromCallback[UIEvent](cb => tag(attr := cb)) + val todoInputField: Input = handler.value.render + + // observer to prevent form submit and empty content + handler.event.observe { e: UIEvent => + e.preventDefault() + if (clear) todoInputField.value = "" + } + + // note that the accessed value is NOT a reactive, there is a name clash with the JS library :-) + val inputFieldText = handler.event.map { _ => todoInputField.value.trim } + + (inputFieldText, todoInputField) + } +} diff --git a/build.sbt b/build.sbt index cf078fc1a..ca7202794 100644 --- a/build.sbt +++ b/build.sbt @@ -162,13 +162,13 @@ lazy val todolist = project.in(file("Modules/Example Todolist")) Dependencies.loci.jsoniterScala, Dependencies.jsoniterScala, jsAcceptUnfairGlobalTasks, - TaskKey[File]("deploy", "generates a correct index.html for the todolist app") := { + TaskKey[File]("deploy", "generates a correct index.template.html for the todolist app") := { val fastlink = (Compile / fastLinkJS).value val jspath = (Compile / fastLinkJS / scalaJSLinkerOutputDirectory).value val bp = baseDirectory.value.toPath val tp = target.value.toPath val template = IO.read(bp.resolve("index.template.html").toFile) - val targetpath = tp.resolve("index.html") + val targetpath = tp.resolve("index.template.html") val jsrel = targetpath.getParent.relativize(jspath.toPath) IO.write(targetpath.toFile, template.replace("JSPATH", s"${jsrel}/main.js")) IO.copyFile(bp.resolve("todolist.css").toFile, tp.resolve("todolist.css").toFile) @@ -176,6 +176,28 @@ lazy val todolist = project.in(file("Modules/Example Todolist")) } ) +lazy val unitConversion = project.in(file("Modules/Example ReactiveLenses")) + .enablePlugins(ScalaJSPlugin) + .dependsOn(rescala.js) + .settings( + scalaVersion_3, + noPublish, + Dependencies.scalatags, + jsAcceptUnfairGlobalTasks, + TaskKey[File]("deploy", "generates a correct index.template.html for the unitconversion app") := { + val fastlink = (Compile / fastLinkJS).value + val jspath = (Compile / fastLinkJS / scalaJSLinkerOutputDirectory).value + val bp = baseDirectory.value.toPath + val tp = target.value.toPath + val template = IO.read(bp.resolve("index.template.html").toFile) + val targetpath = tp.resolve("index.template.html") + val jsrel = targetpath.getParent.relativize(jspath.toPath) + IO.write(targetpath.toFile, template.replace("JSPATH", s"${jsrel}/main.js")) + //IO.copyFile(bp.resolve("todolist.css").toFile, tp.resolve("todolist.css").toFile) + targetpath.toFile + } + ) + lazy val encryptedTodo = project.in(file("Modules/Example EncryptedTodoFx")) .enablePlugins(JmhPlugin) .dependsOn(kofre.jvm) @@ -218,13 +240,13 @@ lazy val replicationExamples = .jsSettings( Dependencies.scalatags, Dependencies.loci.wsWeb, - TaskKey[File]("deploy", "generates a correct index.html") := { + TaskKey[File]("deploy", "generates a correct index.template.html") := { val fastlink = (Compile / fastLinkJS).value val jspath = (Compile / fastLinkJS / scalaJSLinkerOutputDirectory).value val bp = baseDirectory.value.toPath val tp = jspath.toPath val template = IO.read(bp.resolve("index.template.html").toFile) - val targetpath = tp.resolve("index.html").toFile + val targetpath = tp.resolve("index.template.html").toFile IO.write(targetpath, template.replace("JSPATH", s"main.js")) IO.copyFile(bp.resolve("style.css").toFile, tp.resolve("style.css").toFile) targetpath From e5d55b0e7dd20e1a06310eaeb757cb5585141541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Thu, 16 Nov 2023 15:47:28 +0100 Subject: [PATCH 02/33] Fixed Integration --- .../index.template.html | 3 ++ .../src/main/scala/copl/ConversionTest.scala | 30 +++++++++++-------- .../src/main/scala/copl/RenderUtil.scala | 8 ++--- build.sbt | 5 ++-- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/Modules/Example ReactiveLenses/index.template.html b/Modules/Example ReactiveLenses/index.template.html index eca439bcc..712677ef1 100644 --- a/Modules/Example ReactiveLenses/index.template.html +++ b/Modules/Example ReactiveLenses/index.template.html @@ -13,5 +13,8 @@ + diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 19a3a4444..b15fc967a 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -1,19 +1,28 @@ package copl -import geny.Generator.from -import org.scalajs.dom.html.{Div, Input, Paragraph} -import org.scalajs.dom.{UIEvent, document, window} -import rescala.default.* -import rescala.extra.Tags.* +import org.scalajs.dom.html.{Input, Paragraph} +import org.scalajs.dom.document +import rescala.extra.Tags +import rescala.interfaces.toposort.* import scalatags.JsDom import scalatags.JsDom.all.* -import scalatags.JsDom.{Attr, TypedTag} +import scalatags.JsDom.TypedTag + +import scala.scalajs.js.annotation.JSExportTopLevel + +object TopoTags extends Tags[rescala.interfaces.toposort.type](rescala.interfaces.toposort, true) +import TopoTags.* object ConversionTest { + @JSExportTopLevel("UnitConversion") + def run(): Unit = main(Array.empty[String]) + def main(args: Array[String]): Unit = { - val oneWayConverter = getOneWayConverter() - document.body.replaceChild(oneWayConverter.render, document.body.firstChild) + println("hello") + //val oneWayConverter = getOneWayConverter() + //document.body.replaceChild(oneWayConverter.render, document.body.firstChild) + //() } def getOneWayConverter() = { @@ -21,7 +30,6 @@ object ConversionTest { val meterInput: TypedTag[Input] = input(placeholder := "Meters") val (meterEvent: Event[String], renderedMeter: Input) = RenderUtil.inputFieldHandler(meterInput, oninput, clear = false) - //Q: Used to be latest val yardSignal: Signal[Option[Double]] = meterEvent.hold(init="Please enter a valid value for meters.").map { str => convertMeterToYard(str.toDoubleOption)} val yardParagraph: Signal[TypedTag[Paragraph]] = yardSignal.map { yard => p(if (yard.isEmpty) "Please enter a valid value for meters." else yard.get.toString)} @@ -37,7 +45,3 @@ object ConversionTest { } } - -//Q: RenderUtil not working -//Q: LensBundle in rescala.operator -//Q: Integration of TopoBundle diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala index 0fbf7ed55..8416b096b 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala @@ -2,17 +2,17 @@ package copl import org.scalajs.dom.UIEvent import org.scalajs.dom.html.Input -import rescala.default._ +import rescala.interfaces.toposort.* import scalatags.JsDom.all.* import scalatags.JsDom.{Attr, TypedTag} object RenderUtil { def inputFieldHandler(tag: TypedTag[Input], attr: Attr, clear: Boolean = true): (Event[String], Input) = { - val handler = Events.fromCallback[UIEvent](cb => tag(attr := cb)) - val todoInputField: Input = handler.value.render + val handler = Event.fromCallback(tag(attr := Event.handle[UIEvent])) + val todoInputField: Input = handler.data.render // observer to prevent form submit and empty content - handler.event.observe { e: UIEvent => + handler.event.observe { (e: UIEvent) => e.preventDefault() if (clear) todoInputField.value = "" } diff --git a/build.sbt b/build.sbt index ca7202794..e4122a417 100644 --- a/build.sbt +++ b/build.sbt @@ -21,6 +21,7 @@ lazy val rescalaProject = project.in(file(".")).settings(noPublish).aggregate( // examples & case studies examples, todolist, + unitConversion, encryptedTodo, replicationExamples.js, replicationExamples.jvm, @@ -168,7 +169,7 @@ lazy val todolist = project.in(file("Modules/Example Todolist")) val bp = baseDirectory.value.toPath val tp = target.value.toPath val template = IO.read(bp.resolve("index.template.html").toFile) - val targetpath = tp.resolve("index.template.html") + val targetpath = tp.resolve("index.html") val jsrel = targetpath.getParent.relativize(jspath.toPath) IO.write(targetpath.toFile, template.replace("JSPATH", s"${jsrel}/main.js")) IO.copyFile(bp.resolve("todolist.css").toFile, tp.resolve("todolist.css").toFile) @@ -190,7 +191,7 @@ lazy val unitConversion = project.in(file("Modules/Example ReactiveLenses")) val bp = baseDirectory.value.toPath val tp = target.value.toPath val template = IO.read(bp.resolve("index.template.html").toFile) - val targetpath = tp.resolve("index.template.html") + val targetpath = tp.resolve("index.html") val jsrel = targetpath.getParent.relativize(jspath.toPath) IO.write(targetpath.toFile, template.replace("JSPATH", s"${jsrel}/main.js")) //IO.copyFile(bp.resolve("todolist.css").toFile, tp.resolve("todolist.css").toFile) From 23ac75d0bfe081b2dbe07dd5d1b86877ef54a710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Mon, 27 Nov 2023 11:24:56 +0100 Subject: [PATCH 03/33] signalTest --- .../src/main/scala/copl/ConversionTest.scala | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index b15fc967a..6aec0ea79 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -3,6 +3,7 @@ package copl import org.scalajs.dom.html.{Input, Paragraph} import org.scalajs.dom.document import rescala.extra.Tags +import rescala.interfaces.toposort import rescala.interfaces.toposort.* import scalatags.JsDom import scalatags.JsDom.all.* @@ -19,12 +20,24 @@ object ConversionTest { def run(): Unit = main(Array.empty[String]) def main(args: Array[String]): Unit = { - println("hello") + signalTest() //val oneWayConverter = getOneWayConverter() //document.body.replaceChild(oneWayConverter.render, document.body.firstChild) //() } + def signalTest() = { + + val a = Var(2) + val b = Var(3) + val c = Signal { a.value + b.value } + + a.set(3) + println(a.now) + println(b.now) + println(c.now) + } + def getOneWayConverter() = { val meterInput: TypedTag[Input] = input(placeholder := "Meters") From a4ad612cda67d6ea8c99b2c85d225c0d959ca6a7 Mon Sep 17 00:00:00 2001 From: Josef552 <105931716+Josef552@users.noreply.github.com> Date: Mon, 27 Nov 2023 16:37:53 +0100 Subject: [PATCH 04/33] =?UTF-8?q?datenm=C3=BCll?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scala/rescala/operator/LensBundle.scala | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala new file mode 100644 index 000000000..44022770f --- /dev/null +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala @@ -0,0 +1,58 @@ +package rescala.operator + +trait LensBundle { + trait Lens[M, V] { + def toView(m:M) : V + def toModel(v: V, m: M): M + + def compose[W](lens: Lens[V,W]): Lens[M,W] + } + + trait BijectiveLens[M, V] extends Lens[M, V]{ + def toView(m: M): V + def toModel(v: V): M + def toModel(v: V, m: M): M = toModel(v) + + def inverse: BijectiveLens[V, M] + def compose[W](that: BijectiveLens[V, W]): BijectiveLens[M, W] + } + + class AddLens[A](k: A)(implicit num: Numeric[A]) extends BijectiveLens[A, A]{ + def toView(m: A): A = num.plus(m, k) + def toModel(v: A): A = num.minus(v, k) + } + + //like this? + class SubtractLens[A](k: A)(implicit num: Numeric[A]) extends BijectiveLens[A, A] { + def toView(m: A): A = num.minus(m, k) + def toModel(v: A): A = num.plus(v, k) + } + + class MulLens[A](k: A)(implicit frac: Fractional[A]) extends Lens[A, A] { + if(k==0) throw new IllegalArgumentException("Illegal Lens: mul/div by zero") + + def toView(m: A): A = frac.times(m, k) + def toModel(v: A, m: A): A = { + val res = frac.div(v, k) + if(frac.equiv(res, m)) m + else res + } + } + + //analog auch für andere arithmetische Operations-Lenses + //Division, Exponential, Wurzel, Log (?) + + /** + //das war ja eher nur einen auf fancy shmancy machen, also fuer uns eher irrelevant (?) + def LVar[A](init: A): LVar[A] + def applyLens[B](lens: Lens[A, B]): LVar[B] + + implicit def toFracLVar[A: Fractional](lvar: LVar[A]) = new FractionalLVar(lvar) + + class FractionalLVar[A: Fractional](lvar: LVar[A]) { + def *(k:A) = lvar.applyLens(new MulLens(k)) + } + **/ + + +} From c08818935c5386ac561ec0e90a54aa62fda3bc20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Mon, 27 Nov 2023 16:38:16 +0100 Subject: [PATCH 05/33] testPrint --- .../shared/src/main/scala/rescala/scheduler/TopoBundle.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala index 295603e28..6b8fe5ab1 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala @@ -64,6 +64,11 @@ trait TopoBundle { reactive.state.dirty = needsReevaluation createdReactives :+= reactive + for (in <- incoming) { + println(in.state.value) + println(in.state.value) + } + val predecessorsDone = incoming.forall(r => !r.state.discovered || r.state.done) // requires reev, any predecessor is dirty, but all discovered predecessors are already done val requiresReev = incoming.exists(_.state.dirty) && predecessorsDone From 7b20d7dbd89a4ae51b6f9e92897ff0ef44f92310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Mon, 4 Dec 2023 15:11:18 +0100 Subject: [PATCH 06/33] Added simple reset of Vars after Signal evaluation --- .../src/main/scala/copl/ConversionTest.scala | 13 +- .../scala/rescala/operator/LensBundle.scala | 125 ++++++++++-------- .../scala/rescala/operator/SourceBundle.scala | 27 ++++ .../scala/rescala/scheduler/TopoBundle.scala | 20 +-- 4 files changed, 117 insertions(+), 68 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 6aec0ea79..2d26ede36 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -28,9 +28,16 @@ object ConversionTest { def signalTest() = { - val a = Var(2) - val b = Var(3) - val c = Signal { a.value + b.value } + val a = LVar(2) + val b = LVar(3) + + println(a.now) + println(b.now) + + val c = Signal { a.value + b.value } // { a() + b() } + println(a.now) + println(b.now) + println(c.now) a.set(3) println(a.now) diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala index 44022770f..bcf071bb1 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala @@ -1,58 +1,71 @@ -package rescala.operator +//package rescala.operator +// +//trait LensBundle { +// //selfType: Operators => +// +// trait Lens[M, V] { +// def toView(m: M): V +// +// def toModel(v: V, m: M): M +// } +// +// trait BijectiveLens[M, V] extends Lens[M, V] { +// def toView(m: M): V +// +// def toModel(v: V): M +// +// def toModel(v: V, m: M): M = toModel(v) +// } +// +// class AddLens[A](k: A)(implicit num: Numeric[A]) extends BijectiveLens[A, A] { +// def toView(m: A): A = num.plus(m, k) +// +// def toModel(v: A): A = num.minus(v, k) +// } -trait LensBundle { - trait Lens[M, V] { - def toView(m:M) : V - def toModel(v: V, m: M): M - def compose[W](lens: Lens[V,W]): Lens[M,W] - } - - trait BijectiveLens[M, V] extends Lens[M, V]{ - def toView(m: M): V - def toModel(v: V): M - def toModel(v: V, m: M): M = toModel(v) - - def inverse: BijectiveLens[V, M] - def compose[W](that: BijectiveLens[V, W]): BijectiveLens[M, W] - } - - class AddLens[A](k: A)(implicit num: Numeric[A]) extends BijectiveLens[A, A]{ - def toView(m: A): A = num.plus(m, k) - def toModel(v: A): A = num.minus(v, k) - } - - //like this? - class SubtractLens[A](k: A)(implicit num: Numeric[A]) extends BijectiveLens[A, A] { - def toView(m: A): A = num.minus(m, k) - def toModel(v: A): A = num.plus(v, k) - } - - class MulLens[A](k: A)(implicit frac: Fractional[A]) extends Lens[A, A] { - if(k==0) throw new IllegalArgumentException("Illegal Lens: mul/div by zero") - - def toView(m: A): A = frac.times(m, k) - def toModel(v: A, m: A): A = { - val res = frac.div(v, k) - if(frac.equiv(res, m)) m - else res - } - } - - //analog auch für andere arithmetische Operations-Lenses - //Division, Exponential, Wurzel, Log (?) - - /** - //das war ja eher nur einen auf fancy shmancy machen, also fuer uns eher irrelevant (?) - def LVar[A](init: A): LVar[A] - def applyLens[B](lens: Lens[A, B]): LVar[B] - - implicit def toFracLVar[A: Fractional](lvar: LVar[A]) = new FractionalLVar(lvar) - - class FractionalLVar[A: Fractional](lvar: LVar[A]) { - def *(k:A) = lvar.applyLens(new MulLens(k)) - } - **/ - - -} +// class LVar extends Var() +// +// //das war ja eher nur einen auf fancy shmancy machen, also fuer uns eher irrelevant (?) +// def LVar[A](init: A): LVar[A] +// def applyLens[B](lens: Lens[A, B]): LVar[B] +// +// class FractionalLVar[A: Fractional](lvar: LVar[A]) { +// def *(k:A) = lvar.applyLens(new MulLens(k)) +// } +//} +// +// //like this? +// class SubtractLens[A](k: A)(implicit num: Numeric[A]) extends BijectiveLens[A, A] { +// def toView(m: A): A = num.minus(m, k) +// def toModel(v: A): A = num.plus(v, k) +// } +// +// class MulLens[A](k: A)(implicit frac: Fractional[A]) extends Lens[A, A] { +// if(k==0) throw new IllegalArgumentException("Illegal Lens: mul/div by zero") +// +// def toView(m: A): A = frac.times(m, k) +// def toModel(v: A, m: A): A = { +// val res = frac.div(v, k) +// if(frac.equiv(res, m)) m +// else res +// } +// } +// +// //analog auch für andere arithmetische Operations-Lenses +// //Division, Exponential, Wurzel, Log (?) +// +// /** +// //das war ja eher nur einen auf fancy shmancy machen, also fuer uns eher irrelevant (?) +// def LVar[A](init: A): LVar[A] +// def applyLens[B](lens: Lens[A, B]): LVar[B] +// +// implicit def toFracLVar[A: Fractional](lvar: LVar[A]) = new FractionalLVar(lvar) +// +// class FractionalLVar[A: Fractional](lvar: LVar[A]) { +// def *(k:A) = lvar.applyLens(new MulLens(k)) +// } +// **/ +// +// +//} diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index ecba57390..202f14ac0 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -109,4 +109,31 @@ trait SourceBundle { } } + trait Lens[M, V] { + def toView(m: M): V + def toModel(v: V, m: M): M + } + + trait BijectiveLens[M, V] extends Lens[M, V] { + def toView(m: M): V + def toModel(v: V): M + def toModel(v: V, m: M): M = toModel(v) + } + + class AddLens[A](k: A)(implicit num: Numeric[A]) extends BijectiveLens[A, A] { + def toView(m: A): A = num.plus(m, k) + def toModel(v: A): A = num.minus(v, k) + } + + class LVar[A] private[rescala] (initialState: BundleState[Pulse[A]], name: ReInfo) + extends Var(initialState, name){} + + object LVar { + def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = fromChange(Pulse.Value(initval)) + def empty[T](implicit ticket: CreationTicket[BundleState]): LVar[T] = fromChange(Pulse.empty) + private[this] def fromChange[T](change: Pulse[T])(implicit ticket: CreationTicket[BundleState]): LVar[T] = { + ticket.createSource[Pulse[T], LVar[T]](change)(s => new LVar[T](s, ticket.info)) + } + } + } diff --git a/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala index 6b8fe5ab1..7e425ba52 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala @@ -1,9 +1,8 @@ package rescala.scheduler -import rescala.core.{ - AccessHandler, AdmissionTicket, Initializer, Observation, ReSource, ReadAs, ReevTicket, SchedulerImpl, - Transaction -} +import rescala.core.{AccessHandler, AdmissionTicket, Initializer, Observation, ReSource, ReadAs, ReevTicket, SchedulerImpl, Transaction} +import rescala.structure.Pulse +import rescala.operator.SignalBundle import scala.collection.mutable.{ArrayBuffer, ListBuffer} @@ -64,11 +63,6 @@ trait TopoBundle { reactive.state.dirty = needsReevaluation createdReactives :+= reactive - for (in <- incoming) { - println(in.state.value) - println(in.state.value) - } - val predecessorsDone = incoming.forall(r => !r.state.discovered || r.state.done) // requires reev, any predecessor is dirty, but all discovered predecessors are already done val requiresReev = incoming.exists(_.state.dirty) && predecessorsDone @@ -142,6 +136,14 @@ trait TopoBundle { Util.evaluateAll(created, transaction, afterCommitObservers).foreach(reset) assert(creation.drainCreated().isEmpty) + //Reset Vars + if (admissionResult.isInstanceOf[ReSource]) { + val resState = admissionResult.asInstanceOf[ReSource] + for (in <- resState.state.incoming) { + in.state.value = Pulse.Value(0).asInstanceOf[in.Value] + } + } + beforeCleanupHook(created ++ sorted ++ initialWrites, initialWrites) // cleanup From 4e65374f47a8ff3d08be1001abd3ed38317a0295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Mon, 4 Dec 2023 17:16:11 +0100 Subject: [PATCH 07/33] Only reset LVars --- .../src/main/scala/copl/ConversionTest.scala | 4 ++-- .../src/main/scala/rescala/scheduler/TopoBundle.scala | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 2d26ede36..33847d4dc 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -29,7 +29,7 @@ object ConversionTest { def signalTest() = { val a = LVar(2) - val b = LVar(3) + val b = Var(3) println(a.now) println(b.now) @@ -39,7 +39,7 @@ object ConversionTest { println(b.now) println(c.now) - a.set(3) + a.set(1) println(a.now) println(b.now) println(c.now) diff --git a/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala index 7e425ba52..0d7acd49e 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala @@ -2,7 +2,7 @@ package rescala.scheduler import rescala.core.{AccessHandler, AdmissionTicket, Initializer, Observation, ReSource, ReadAs, ReevTicket, SchedulerImpl, Transaction} import rescala.structure.Pulse -import rescala.operator.SignalBundle +import rescala.operator.SourceBundle import scala.collection.mutable.{ArrayBuffer, ListBuffer} @@ -140,7 +140,9 @@ trait TopoBundle { if (admissionResult.isInstanceOf[ReSource]) { val resState = admissionResult.asInstanceOf[ReSource] for (in <- resState.state.incoming) { - in.state.value = Pulse.Value(0).asInstanceOf[in.Value] + if(in.isInstanceOf[SourceBundle#LVar[Any]]) { + in.state.value = Pulse.Value(0).asInstanceOf[in.Value] + } } } From c1571910892cff35567ab11115b446e178745dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Wed, 6 Dec 2023 14:21:22 +0100 Subject: [PATCH 08/33] Added concept.txt --- Modules/Example ReactiveLenses/concept.txt | 30 +++++++++++++++++++ .../src/main/scala/copl/ConversionTest.scala | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 Modules/Example ReactiveLenses/concept.txt diff --git a/Modules/Example ReactiveLenses/concept.txt b/Modules/Example ReactiveLenses/concept.txt new file mode 100644 index 000000000..500218fb8 --- /dev/null +++ b/Modules/Example ReactiveLenses/concept.txt @@ -0,0 +1,30 @@ +Points: + 1. Propagation richtung Root + 2. How to do toModel() + +Idee: + 1. If LVar changed -> Propagate using toModel() Function -> where are the dependencies? + 2. Propagate Signals & toViews() -> multiple Points of Change or just from root and evaluate vars multiple times + +Signals vs Lenses + + Signals Lenses + n -> 1 1 <-> 1 + expr(Vars) -> Signal expr1(LVar) -> LVar + LVar <- expr2(LVar) + Signal = Signal {...} LVar[View] = LVar[Model].applyLens(...) / Lens(LVar) z.B. meters = yards.applyLens(new MulLens(0.9144)) + +What happens when an update occurs? + + Signal => all dependencies are reevaluated + Var => inherits from Signal, but updates can be triggered manually + LVar => inherits from Var AND + all LVars connected via Lenses are updated using toView() OR toModel() + +How to decide which method to call? + + 1. no change in dependency structure + toView() "trivially" as Signal expression + toModel() if "incoming" is connected via Lens, call toModel() -> How do we know that? & Do Vars have "incoming"? + + 2. extend dependencies bidirectional, if LVar in "incoming" && "outgoing" we have found a Lens -> What's the orientation? diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 33847d4dc..ef956bf61 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -34,7 +34,7 @@ object ConversionTest { println(a.now) println(b.now) - val c = Signal { a.value + b.value } // { a() + b() } + val c: toposort.Signal[Int] = Signal { a.value + b.value } // { a() + b() } println(a.now) println(b.now) println(c.now) From ef958be412916eab34d49dacbf68f9ccc1d31084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Mon, 11 Dec 2023 22:06:01 +0100 Subject: [PATCH 09/33] Started Signal Implementation --- .../src/main/scala/copl/ConversionTest.scala | 33 ++++++++----- .../scala/rescala/operator/SourceBundle.scala | 48 ++++++++++++++----- .../scala/rescala/scheduler/TopoBundle.scala | 18 +++---- 3 files changed, 68 insertions(+), 31 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index ef956bf61..d459f0551 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -29,20 +29,31 @@ object ConversionTest { def signalTest() = { val a = LVar(2) - val b = Var(3) - +// val b = Var(3) +// +// println(a.now) +// println(b.now) +// +// val c: toposort.Signal[Int] = Signal { a.value + b.value } // { a() + b() } +// println(a.now) +// println(b.now) +// println(c.now) +// +// a.set(1) +// println(a.now) +// println(b.now) +// println(c.now) + + println("Starting Lens Tests") + val d = a.applyLens(new AddLens(10)) println(a.now) - println(b.now) - - val c: toposort.Signal[Int] = Signal { a.value + b.value } // { a() + b() } + println(d.now) + d.set(20) println(a.now) - println(b.now) - println(c.now) +// println(b.now) +// println(c.now) + println(d.now) - a.set(1) - println(a.now) - println(b.now) - println(c.now) } def getOneWayConverter() = { diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index 202f14ac0..bc6d58ea8 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -1,8 +1,6 @@ package rescala.operator -import rescala.core.{ - AdmissionTicket, Base, CreationTicket, InitialChange, Observation, ReInfo, ReSource, Scheduler, ScopeSearch -} +import rescala.core.{AdmissionTicket, Base, CreationTicket, InitialChange, Observation, ReInfo, ReSource, Scheduler, ScopeSearch} import rescala.structure.Pulse trait SourceBundle { @@ -109,6 +107,38 @@ trait SourceBundle { } } + class LVar[M] private[rescala](initialState: BundleState[Pulse[M]], name: ReInfo, model : Var[M], lens: Lens[M, M]) + extends Var(initialState, name) { + + override def set(value : M)(implicit sched: Scheduler[State], scopeSearch: ScopeSearch[State]): Unit = { + model.set(lens.toModel(value, this.now)) + } + + def applyLens(lens: Lens[M, M])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[State]): LVar[M] = { + ticket.createSource[Pulse[M], LVar[M]](Pulse.Value(lens.toView(this.now)))(s => new LVar[M](s, ticket.info, this, lens)) +// val (inputs, fun, isStatic) = +// rescala.macros.getDependencies[T, ReSource.of[BundleState], rescala.core.StaticTicket[BundleState], true](expr) +// ticket.create[Pulse[M], SignalImpl[BundleState, M] with Signal[M]]( +// inputs.toSet, +// Pulse.empty, +// needsReevaluation = true +// ) { +// state => new SignalImpl(state, (t, _) => expr(t), ct.info, None) with Signal[T] +// } + } + + } + + object LVar { + def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = fromChange(Pulse.Value(initval)) + + def empty[T](implicit ticket: CreationTicket[BundleState]): LVar[T] = fromChange(Pulse.empty) + + private[this] def fromChange[T](change: Pulse[T])(implicit ticket: CreationTicket[BundleState]): LVar[T] = { + ticket.createSource[Pulse[T], LVar[T]](change)(s => new LVar[T](s, ticket.info, new Var[T](s, ticket.info), new NeutralLens[T]())) + } + } + trait Lens[M, V] { def toView(m: M): V def toModel(v: V, m: M): M @@ -125,15 +155,11 @@ trait SourceBundle { def toModel(v: A): A = num.minus(v, k) } - class LVar[A] private[rescala] (initialState: BundleState[Pulse[A]], name: ReInfo) - extends Var(initialState, name){} + class NeutralLens[A] extends BijectiveLens[A, A] { + def toView(m: A): A = m - object LVar { - def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = fromChange(Pulse.Value(initval)) - def empty[T](implicit ticket: CreationTicket[BundleState]): LVar[T] = fromChange(Pulse.empty) - private[this] def fromChange[T](change: Pulse[T])(implicit ticket: CreationTicket[BundleState]): LVar[T] = { - ticket.createSource[Pulse[T], LVar[T]](change)(s => new LVar[T](s, ticket.info)) - } + def toModel(v: A): A = v } + } diff --git a/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala index 0d7acd49e..d54481e32 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala @@ -136,15 +136,15 @@ trait TopoBundle { Util.evaluateAll(created, transaction, afterCommitObservers).foreach(reset) assert(creation.drainCreated().isEmpty) - //Reset Vars - if (admissionResult.isInstanceOf[ReSource]) { - val resState = admissionResult.asInstanceOf[ReSource] - for (in <- resState.state.incoming) { - if(in.isInstanceOf[SourceBundle#LVar[Any]]) { - in.state.value = Pulse.Value(0).asInstanceOf[in.Value] - } - } - } +// //Reset Vars +// if (admissionResult.isInstanceOf[ReSource]) { +// val resState = admissionResult.asInstanceOf[ReSource] +// for (in <- resState.state.incoming) { +// if(in.isInstanceOf[SourceBundle#LVar[Any]]) { +// in.state.value = Pulse.Value(0).asInstanceOf[in.Value] +// } +// } +// } beforeCleanupHook(created ++ sorted ++ initialWrites, initialWrites) From 5dd1abc49fc1636b5ff99159e71ac5776cf84ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Wed, 13 Dec 2023 15:18:02 +0100 Subject: [PATCH 10/33] Implemented Lenses between identical types; cannot be used in reacitves yet --- .../src/main/scala/copl/ConversionTest.scala | 39 ++++++------- .../scala/rescala/operator/SourceBundle.scala | 57 +++++++++++-------- 2 files changed, 49 insertions(+), 47 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index d459f0551..4c4837888 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -28,32 +28,27 @@ object ConversionTest { def signalTest() = { + println("Init Vars") val a = LVar(2) -// val b = Var(3) -// -// println(a.now) -// println(b.now) -// -// val c: toposort.Signal[Int] = Signal { a.value + b.value } // { a() + b() } -// println(a.now) -// println(b.now) -// println(c.now) -// -// a.set(1) -// println(a.now) -// println(b.now) -// println(c.now) - - println("Starting Lens Tests") - val d = a.applyLens(new AddLens(10)) + val b = a.applyLens(new AddLens(10)) + val c = a.applyLens(new AddLens(-1)) + //val c = a.applyLens(new CharCountLens()) println(a.now) - println(d.now) - d.set(20) + println(b.now) + println(c.now) + + println("Changing b") + b.set(0) println(a.now) -// println(b.now) -// println(c.now) - println(d.now) + println(b.now) + println(c.now) + println("Changing c") + //c.set("abc") + c.set(5) + println(a.now) + println(b.now) + println(c.now) } def getOneWayConverter() = { diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index bc6d58ea8..b5a8f13a0 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -1,7 +1,9 @@ package rescala.operator import rescala.core.{AdmissionTicket, Base, CreationTicket, InitialChange, Observation, ReInfo, ReSource, Scheduler, ScopeSearch} -import rescala.structure.Pulse +import rescala.structure.{Pulse, SignalImpl} + +import scala.util.Random trait SourceBundle { self: Operators => @@ -107,36 +109,42 @@ trait SourceBundle { } } - class LVar[M] private[rescala](initialState: BundleState[Pulse[M]], name: ReInfo, model : Var[M], lens: Lens[M, M]) - extends Var(initialState, name) { + class LVar[M] private[rescala](model : LVar[M], lens: BijectiveLens[M, M], internal : Signal[M]) { + + def set(value: M)(implicit sched: Scheduler[internal.State]): Unit = { + model.set(lens.toModel(value)) + } - override def set(value : M)(implicit sched: Scheduler[State], scopeSearch: ScopeSearch[State]): Unit = { - model.set(lens.toModel(value, this.now)) + def applyLens(lens : BijectiveLens[M, M])(implicit ticket: CreationTicket[BundleState]) : LVar[M] = { + new LVar[M](this, lens, Signal{lens.toView(internal.value)}) } - def applyLens(lens: Lens[M, M])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[State]): LVar[M] = { - ticket.createSource[Pulse[M], LVar[M]](Pulse.Value(lens.toView(this.now)))(s => new LVar[M](s, ticket.info, this, lens)) -// val (inputs, fun, isStatic) = -// rescala.macros.getDependencies[T, ReSource.of[BundleState], rescala.core.StaticTicket[BundleState], true](expr) -// ticket.create[Pulse[M], SignalImpl[BundleState, M] with Signal[M]]( -// inputs.toSet, -// Pulse.empty, -// needsReevaluation = true -// ) { -// state => new SignalImpl(state, (t, _) => expr(t), ct.info, None) with Signal[T] -// } + def now(implicit sched: Scheduler[internal.State]) : M = internal.now + + //How to make value accessible to the outside? + //def value(implicit sched: Scheduler[internal.State]) : M = internal.value + + } + + class RootLVar[M] private[rescala](internal: Var[M]) + extends LVar[M] (null, null, internal) { + + override def set(value: M)(implicit sched: Scheduler[internal.State]): Unit = { + internal.set(value) } } object LVar { - def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = fromChange(Pulse.Value(initval)) - def empty[T](implicit ticket: CreationTicket[BundleState]): LVar[T] = fromChange(Pulse.empty) + def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = { + new RootLVar[T](Var(initval)) + } - private[this] def fromChange[T](change: Pulse[T])(implicit ticket: CreationTicket[BundleState]): LVar[T] = { - ticket.createSource[Pulse[T], LVar[T]](change)(s => new LVar[T](s, ticket.info, new Var[T](s, ticket.info), new NeutralLens[T]())) + def empty[T](implicit ticket: CreationTicket[BundleState]): LVar[T] = { + new RootLVar[T](Var.empty) } + } trait Lens[M, V] { @@ -155,11 +163,10 @@ trait SourceBundle { def toModel(v: A): A = num.minus(v, k) } - class NeutralLens[A] extends BijectiveLens[A, A] { - def toView(m: A): A = m - - def toModel(v: A): A = v + class CharCountLens extends BijectiveLens[Int, String] { + // Lens that converts an int to a sequence of chars, eg. 5 => "aaaaa" + def toView(m: Int): String = Random.alphanumeric.take(m).mkString + def toModel(v: String): Int = v.length } - } From 165dad7ac6b35fdfdf6e51074165fd393bdd00d3 Mon Sep 17 00:00:00 2001 From: Josef552 <105931716+Josef552@users.noreply.github.com> Date: Wed, 20 Dec 2023 11:48:25 +0100 Subject: [PATCH 11/33] einfacher temperatur konverter --- .../src/main/scala/copl/ConversionTest.scala | 44 +++++++++++++++++-- .../scala/rescala/operator/SourceBundle.scala | 8 +++- .../scala/rescala/scheduler/TopoBundle.scala | 4 +- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 4c4837888..6c6ef584f 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -20,14 +20,16 @@ object ConversionTest { def run(): Unit = main(Array.empty[String]) def main(args: Array[String]): Unit = { - signalTest() - //val oneWayConverter = getOneWayConverter() - //document.body.replaceChild(oneWayConverter.render, document.body.firstChild) - //() + //signalTest() + val temperatureConverter = getTemperatureConverter() + document.body.replaceChild(temperatureConverter.render, document.body.firstChild) + () } + def signalTest() = { + /** println("Init Vars") val a = LVar(2) val b = a.applyLens(new AddLens(10)) @@ -49,6 +51,23 @@ object ConversionTest { println(a.now) println(b.now) println(c.now) + **/ + + //val g = LVar(5) + //val h = g.applyLens(new CharCountLens()) + //println(g.now) + //println(h.now) + + /** + val x = LVar("kaesekuchen") + val y = x.applyLens(new UpperCharLens() ) + println(x.now) + println(y.now) + y.set("APFELMUS") + println(x.now) + println(y.now) + **/ + } def getOneWayConverter() = { @@ -63,6 +82,23 @@ object ConversionTest { div(p("One way conversion using Signal"), renderedMeter, yardParagraph.asModifier) } + def getTemperatureConverter() = { + + val celsiusVar = LVar(0.0) + val kelvinVar = celsiusVar.applyLens(new AddLens(273.15)) + + val celsiusInput: TypedTag[Input] = input(value := celsiusVar.now) + val (celsiusEvent: Event[String], renderedCelsius: Input) = RenderUtil.inputFieldHandler(celsiusInput, oninput, clear = false) + + val kelvinInput: TypedTag[Input] = input(value := kelvinVar.now) + val (kelvinEvent: Event[String], renderedKelvin: Input) = RenderUtil.inputFieldHandler(kelvinInput, oninput, clear = false) + + celsiusEvent.observe{ str => celsiusVar.set(str.toDouble); renderedKelvin.value = kelvinVar.now.toString } + kelvinEvent.observe { str => kelvinVar.set(str.toDouble); renderedCelsius.value = celsiusVar.now.toString } + + div(p("Unit Conversion with Lenses :D"), renderedCelsius, renderedKelvin) + } + def convertMeterToYard(meter : Option[Double]): Option[Double] = { if(meter.isEmpty) Option.empty[Double] diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index b5a8f13a0..066ab80a1 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -1,7 +1,7 @@ package rescala.operator import rescala.core.{AdmissionTicket, Base, CreationTicket, InitialChange, Observation, ReInfo, ReSource, Scheduler, ScopeSearch} -import rescala.structure.{Pulse, SignalImpl} +import rescala.structure.Pulse import scala.util.Random @@ -169,4 +169,10 @@ trait SourceBundle { def toModel(v: String): Int = v.length } + class UpperCharLens extends BijectiveLens[String, String]{ + //shift up a char's letters to all caps + //würde aber eh noch so "if-checks" benötigen, dass man vorher überprüft obs schon upperCase ist or not + def toView(m: String): String = m.toUpperCase() + def toModel(v: String): String = v.toLowerCase() + } } diff --git a/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala index d54481e32..31f9912c4 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/scheduler/TopoBundle.scala @@ -1,8 +1,8 @@ package rescala.scheduler import rescala.core.{AccessHandler, AdmissionTicket, Initializer, Observation, ReSource, ReadAs, ReevTicket, SchedulerImpl, Transaction} -import rescala.structure.Pulse -import rescala.operator.SourceBundle +//import rescala.structure.Pulse +//import rescala.operator.SourceBundle import scala.collection.mutable.{ArrayBuffer, ListBuffer} From 7b6143bebe481b2ee8e58dc65d378955acae315d Mon Sep 17 00:00:00 2001 From: Josef552 <105931716+Josef552@users.noreply.github.com> Date: Wed, 20 Dec 2023 12:29:27 +0100 Subject: [PATCH 12/33] experimental --- .../src/main/scala/copl/ConversionTest.scala | 18 ++++++++++++++++++ .../scala/rescala/operator/SourceBundle.scala | 6 +++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 6c6ef584f..a122da20a 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -99,6 +99,24 @@ object ConversionTest { div(p("Unit Conversion with Lenses :D"), renderedCelsius, renderedKelvin) } + def toStringConverter() = { + + val intVar = LVar(0) + val strVar = celsiusVar.applyLens(new CharCountLens()) + + val intInput: TypedTag[Input] = input(value := intVar.now) + val (intEvent: Event[String], renderedInt: Input) = RenderUtil.inputFieldHandler(intInput, oninput, clear = false) + + val strInput: TypedTag[Input] = input(value := strVar.now) + val (strEvent: Event[String], renderedStr: Input) = RenderUtil.inputFieldHandler(strInput, oninput, clear = false) + + intEvent.observe { str => intVar.set(str.toDouble); renderedStr.value = strVar.now } + strEvent.observe { str => strVar.set(str.toDouble); renderedInt.value = celsiusVar.now.toString } + + div(p("Unit Conversion with Lenses :D"), renderedInt, renderedStr) + } + + def convertMeterToYard(meter : Option[Double]): Option[Double] = { if(meter.isEmpty) Option.empty[Double] diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index 066ab80a1..a2af1f39c 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -109,14 +109,14 @@ trait SourceBundle { } } - class LVar[M] private[rescala](model : LVar[M], lens: BijectiveLens[M, M], internal : Signal[M]) { + class LVar[M] private[rescala](model : LVar[_], lens: BijectiveLens[_ , M], internal : Signal[M]) { def set(value: M)(implicit sched: Scheduler[internal.State]): Unit = { model.set(lens.toModel(value)) } - def applyLens(lens : BijectiveLens[M, M])(implicit ticket: CreationTicket[BundleState]) : LVar[M] = { - new LVar[M](this, lens, Signal{lens.toView(internal.value)}) + def applyLens[V](lens : BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState]) : LVar[V] = { + new LVar[V](this, lens, Signal{lens.toView(internal.value)}) } def now(implicit sched: Scheduler[internal.State]) : M = internal.now From 3f40aae02986236be4547a438c3977c5c4d9c41c Mon Sep 17 00:00:00 2001 From: Josef552 <105931716+Josef552@users.noreply.github.com> Date: Wed, 20 Dec 2023 18:01:34 +0100 Subject: [PATCH 13/33] fixed Lenses between two different types --- .../src/main/scala/copl/ConversionTest.scala | 13 +++--- .../scala/rescala/operator/SourceBundle.scala | 46 +++++++++++++++++-- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index a122da20a..a38cb44d1 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -21,7 +21,7 @@ object ConversionTest { def main(args: Array[String]): Unit = { //signalTest() - val temperatureConverter = getTemperatureConverter() + val temperatureConverter = toStringConverter() document.body.replaceChild(temperatureConverter.render, document.body.firstChild) () } @@ -95,14 +95,15 @@ object ConversionTest { celsiusEvent.observe{ str => celsiusVar.set(str.toDouble); renderedKelvin.value = kelvinVar.now.toString } kelvinEvent.observe { str => kelvinVar.set(str.toDouble); renderedCelsius.value = celsiusVar.now.toString } - + div(p("Unit Conversion with Lenses :D"), renderedCelsius, renderedKelvin) } + def toStringConverter() = { val intVar = LVar(0) - val strVar = celsiusVar.applyLens(new CharCountLens()) + val strVar = intVar.applyLens(new CharCountLens()) val intInput: TypedTag[Input] = input(value := intVar.now) val (intEvent: Event[String], renderedInt: Input) = RenderUtil.inputFieldHandler(intInput, oninput, clear = false) @@ -110,12 +111,12 @@ object ConversionTest { val strInput: TypedTag[Input] = input(value := strVar.now) val (strEvent: Event[String], renderedStr: Input) = RenderUtil.inputFieldHandler(strInput, oninput, clear = false) - intEvent.observe { str => intVar.set(str.toDouble); renderedStr.value = strVar.now } - strEvent.observe { str => strVar.set(str.toDouble); renderedInt.value = celsiusVar.now.toString } + intEvent.observe { str => intVar.set(str.toInt); renderedStr.value = strVar.now } + strEvent.observe { str => strVar.set(str); renderedInt.value = intVar.now.toString } div(p("Unit Conversion with Lenses :D"), renderedInt, renderedStr) } - + def convertMeterToYard(meter : Option[Double]): Option[Double] = { if(meter.isEmpty) diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index a2af1f39c..3fa5187fc 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -109,8 +109,8 @@ trait SourceBundle { } } - class LVar[M] private[rescala](model : LVar[_], lens: BijectiveLens[_ , M], internal : Signal[M]) { - + class LVar[M] private[rescala](model : LVar[?], lens: BijectiveLens[model.T , M], internal : Signal[M]) { + type T = M def set(value: M)(implicit sched: Scheduler[internal.State]): Unit = { model.set(lens.toModel(value)) } @@ -122,10 +122,50 @@ trait SourceBundle { def now(implicit sched: Scheduler[internal.State]) : M = internal.now //How to make value accessible to the outside? - //def value(implicit sched: Scheduler[internal.State]) : M = internal.value + inline def value(implicit sched: Scheduler[internal.State]) : M = internal.value } + +// class LVar[M] private[rescala](model : LVar[_], lens: BijectiveLens[_ , M], initialState: BundleState[Pulse[M]], name: ReInfo) +// extends Var[M](initialState, name){ +// def applyLens[V](lens: BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState]): LVar[V] = { +// val expr = lens.toView(value) +// val (sources, fun, isStatic) = +// rescala.macros.getDependencies[V, ReSource.of[BundleState], rescala.core.DynamicTicket[BundleState], false]( +// expr +// ) +// ticket.create[Pulse[V], SignalImpl[BundleState, V] with Signal[V]]( +// sources.toSet, +// Pulse.empty, +// needsReevaluation = true +// ) { +// state => new SignalImpl(state, (t, _) => fun(t), ticket.info, None) with Signal[V] +// } +// } +// +// override def set(value: M)(implicit sched: Scheduler[State]): Unit = { +// if (model == null){ +// super.set(value) +// } else { +// model.set(lens.toModel(value)) +// } +// } +// +// } + +// object LVar { +// +// def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = fromChange(Pulse.Value(initval)) +// +// def empty[T](implicit ticket: CreationTicket[BundleState]): LVar[T] = fromChange(Pulse.empty) +// +// private[this] def fromChange[T](change: Pulse[T])(implicit ticket: CreationTicket[BundleState]): LVar[T] = { +// ticket.createSource[Pulse[T], LVar[T]](change)(s => new LVar[T](null, null, s, ticket.info)) +// } +// +// } + class RootLVar[M] private[rescala](internal: Var[M]) extends LVar[M] (null, null, internal) { From ab525ad976575cbc2a61eaa759db331e642b3b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Thu, 11 Jan 2024 16:42:07 +0100 Subject: [PATCH 14/33] Added Event/Fold based propagation, not working yet --- .../src/main/scala/copl/ConversionTest.scala | 61 ++++++++-------- .../scala/rescala/operator/SourceBundle.scala | 71 +++++-------------- 2 files changed, 47 insertions(+), 85 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index a38cb44d1..6f4ffa641 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -1,5 +1,6 @@ package copl +import jdk.vm.ci.hotspot.HotSpotJVMCICompilerFactory.CompilationLevel import org.scalajs.dom.html.{Input, Paragraph} import org.scalajs.dom.document import rescala.extra.Tags @@ -20,53 +21,48 @@ object ConversionTest { def run(): Unit = main(Array.empty[String]) def main(args: Array[String]): Unit = { - //signalTest() - val temperatureConverter = toStringConverter() - document.body.replaceChild(temperatureConverter.render, document.body.firstChild) + signalTest() +// val temperatureConverter = getTemperatureConverter() +// document.body.replaceChild(temperatureConverter.render, document.body.firstChild) () } def signalTest() = { - /** - println("Init Vars") + val evA: toposort.Evt[Int] = Evt[Int]() + val evB: toposort.Evt[Int] = Evt[Int]() + val evC: toposort.Evt[Int] = Evt[Int]() val a = LVar(2) val b = a.applyLens(new AddLens(10)) val c = a.applyLens(new AddLens(-1)) - //val c = a.applyLens(new CharCountLens()) + a.observe(evA) + b.observe(evB) + c.observe(evC) + a.getEvent().observe{value => println("Value of a changed to " + value)} + + println("Init Vars") + println(a.now) + println(b.now) + println(c.now) + + println("Changing a") + evA.fire(0) println(a.now) println(b.now) println(c.now) println("Changing b") - b.set(0) + evB.fire(0) println(a.now) println(b.now) println(c.now) println("Changing c") - //c.set("abc") - c.set(5) + evC.fire(5) println(a.now) println(b.now) println(c.now) - **/ - - //val g = LVar(5) - //val h = g.applyLens(new CharCountLens()) - //println(g.now) - //println(h.now) - - /** - val x = LVar("kaesekuchen") - val y = x.applyLens(new UpperCharLens() ) - println(x.now) - println(y.now) - y.set("APFELMUS") - println(x.now) - println(y.now) - **/ } @@ -93,10 +89,15 @@ object ConversionTest { val kelvinInput: TypedTag[Input] = input(value := kelvinVar.now) val (kelvinEvent: Event[String], renderedKelvin: Input) = RenderUtil.inputFieldHandler(kelvinInput, oninput, clear = false) - celsiusEvent.observe{ str => celsiusVar.set(str.toDouble); renderedKelvin.value = kelvinVar.now.toString } - kelvinEvent.observe { str => kelvinVar.set(str.toDouble); renderedCelsius.value = celsiusVar.now.toString } +// celsiusEvent.observe{ str => celsiusVar.set(str.toDouble); renderedKelvin.value = kelvinVar.now.toString } +// kelvinEvent.observe { str => kelvinVar.set(str.toDouble); renderedCelsius.value = celsiusVar.now.toString } + + celsiusVar.observe(celsiusEvent.map { str => str.toDouble }) + kelvinVar.observe(kelvinEvent.map { str => str.toDouble }) + + val isFreezing: Signal[TypedTag[Paragraph]] = Signal.dynamic{p(if (celsiusVar.value > 0) "It's not freezing." else "Brrr its so cold.")} - div(p("Unit Conversion with Lenses :D"), renderedCelsius, renderedKelvin) + div(p("Unit Conversion with Lenses :D"), renderedCelsius, renderedKelvin, isFreezing.asModifier) } @@ -111,8 +112,8 @@ object ConversionTest { val strInput: TypedTag[Input] = input(value := strVar.now) val (strEvent: Event[String], renderedStr: Input) = RenderUtil.inputFieldHandler(strInput, oninput, clear = false) - intEvent.observe { str => intVar.set(str.toInt); renderedStr.value = strVar.now } - strEvent.observe { str => strVar.set(str); renderedInt.value = intVar.now.toString } +// intEvent.observe { str => intVar.set(str.toInt); renderedStr.value = strVar.now } +// strEvent.observe { str => strVar.set(str); renderedInt.value = intVar.now.toString } div(p("Unit Conversion with Lenses :D"), renderedInt, renderedStr) } diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index 3fa5187fc..a6b815f78 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -109,16 +109,21 @@ trait SourceBundle { } } - class LVar[M] private[rescala](model : LVar[?], lens: BijectiveLens[model.T , M], internal : Signal[M]) { + class LVar[M] private[rescala](internal : Signal[M], var event : Event[M]) { type T = M - def set(value: M)(implicit sched: Scheduler[internal.State]): Unit = { - model.set(lens.toModel(value)) - } def applyLens[V](lens : BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState]) : LVar[V] = { - new LVar[V](this, lens, Signal{lens.toView(internal.value)}) + val newVar = new LVar[V](Signal{lens.toView(internal.value)}, Evt[V]()) + event = event.||(newVar.getEvent().map(e => lens.toModel(e))) + return newVar + } + + def observe(e: Event[M])(implicit ticket: CreationTicket[BundleState]) : Unit = { + event = event.||(e) } + def getEvent() : Event[M] = event + def now(implicit sched: Scheduler[internal.State]) : M = internal.now //How to make value accessible to the outside? @@ -126,64 +131,20 @@ trait SourceBundle { } - -// class LVar[M] private[rescala](model : LVar[_], lens: BijectiveLens[_ , M], initialState: BundleState[Pulse[M]], name: ReInfo) -// extends Var[M](initialState, name){ -// def applyLens[V](lens: BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState]): LVar[V] = { -// val expr = lens.toView(value) -// val (sources, fun, isStatic) = -// rescala.macros.getDependencies[V, ReSource.of[BundleState], rescala.core.DynamicTicket[BundleState], false]( -// expr -// ) -// ticket.create[Pulse[V], SignalImpl[BundleState, V] with Signal[V]]( -// sources.toSet, -// Pulse.empty, -// needsReevaluation = true -// ) { -// state => new SignalImpl(state, (t, _) => fun(t), ticket.info, None) with Signal[V] -// } -// } -// -// override def set(value: M)(implicit sched: Scheduler[State]): Unit = { -// if (model == null){ -// super.set(value) -// } else { -// model.set(lens.toModel(value)) -// } -// } -// -// } - -// object LVar { -// -// def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = fromChange(Pulse.Value(initval)) -// -// def empty[T](implicit ticket: CreationTicket[BundleState]): LVar[T] = fromChange(Pulse.empty) -// -// private[this] def fromChange[T](change: Pulse[T])(implicit ticket: CreationTicket[BundleState]): LVar[T] = { -// ticket.createSource[Pulse[T], LVar[T]](change)(s => new LVar[T](null, null, s, ticket.info)) -// } -// -// } - - class RootLVar[M] private[rescala](internal: Var[M]) - extends LVar[M] (null, null, internal) { - - override def set(value: M)(implicit sched: Scheduler[internal.State]): Unit = { - internal.set(value) - } + class RootLVar[M] private[rescala](initVal : M, var event: Event[M]) (implicit ticket: CreationTicket[BundleState]) + extends LVar[M] (event.hold(init = initVal), event) { } object LVar { def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = { - new RootLVar[T](Var(initval)) + new RootLVar[T](initval, Evt[T]()) } - def empty[T](implicit ticket: CreationTicket[BundleState]): LVar[T] = { - new RootLVar[T](Var.empty) - } +// def empty[T](implicit ticket: CreationTicket[BundleState]): LVar[T] = { +// new RootLVar[T](Var.empty) +// } } From af4fdc94f47313c525d2e7fdfde97b0dfe10ee5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Fri, 12 Jan 2024 12:41:31 +0100 Subject: [PATCH 15/33] experiments with event list --- .../src/main/scala/copl/ConversionTest.scala | 2 ++ .../scala/rescala/operator/SourceBundle.scala | 22 ++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 6f4ffa641..c6b9a13ca 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -40,6 +40,8 @@ object ConversionTest { b.observe(evB) c.observe(evC) a.getEvent().observe{value => println("Value of a changed to " + value)} + b.getEvent().observe{value => println("Value of b changed to " + value)} + c.getEvent().observe{value => println("Value of c changed to " + value)} println("Init Vars") println(a.now) diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index a6b815f78..3aad9bf43 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -4,6 +4,7 @@ import rescala.core.{AdmissionTicket, Base, CreationTicket, InitialChange, Obser import rescala.structure.Pulse import scala.util.Random +import scala.collection.mutable.ListBuffer trait SourceBundle { self: Operators => @@ -109,20 +110,20 @@ trait SourceBundle { } } - class LVar[M] private[rescala](internal : Signal[M], var event : Event[M]) { + class LVar[M] private[rescala](internal : Signal[M], events : ListBuffer[Event[M]]) { type T = M def applyLens[V](lens : BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState]) : LVar[V] = { - val newVar = new LVar[V](Signal{lens.toView(internal.value)}, Evt[V]()) - event = event.||(newVar.getEvent().map(e => lens.toModel(e))) + val newVar = new LVar[V](Signal{lens.toView(internal.value)}, ListBuffer.empty[Event[V]]) + events += newVar.getEvent().map(e => lens.toModel(e)) return newVar } def observe(e: Event[M])(implicit ticket: CreationTicket[BundleState]) : Unit = { - event = event.||(e) + events += e } - def getEvent() : Event[M] = event + def getEvent() (implicit ticket: CreationTicket[BundleState]) : Event[M] = events.reduce { (a, b) => a || b } def now(implicit sched: Scheduler[internal.State]) : M = internal.now @@ -131,15 +132,16 @@ trait SourceBundle { } - class RootLVar[M] private[rescala](initVal : M, var event: Event[M]) (implicit ticket: CreationTicket[BundleState]) - extends LVar[M] (event.hold(init = initVal), event) { - - } +// class RootLVar[M] private[rescala](initVal : M, var event: Event[M]) (implicit ticket: CreationTicket[BundleState]) +// extends LVar[M] (event.hold(init = initVal), event) { +// +// } object LVar { def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = { - new RootLVar[T](initval, Evt[T]()) + val events = ListBuffer.empty[Event[T]] + new LVar[T](events.reduce { (a, b) => a || b }.hold(init = initval), events) } // def empty[T](implicit ticket: CreationTicket[BundleState]): LVar[T] = { From abd6462d7dd7829aee0aa2234fcb07c666c83e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Fri, 12 Jan 2024 15:34:33 +0100 Subject: [PATCH 16/33] Fixed compiler error --- .../src/main/scala/copl/ConversionTest.scala | 3 +-- .../shared/src/main/scala/rescala/operator/SourceBundle.scala | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index c6b9a13ca..9100f7de0 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -1,8 +1,7 @@ package copl -import jdk.vm.ci.hotspot.HotSpotJVMCICompilerFactory.CompilationLevel import org.scalajs.dom.html.{Input, Paragraph} -import org.scalajs.dom.document +//import org.scalajs.dom.document import rescala.extra.Tags import rescala.interfaces.toposort import rescala.interfaces.toposort.* diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index 3aad9bf43..49dccfc89 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -114,7 +114,7 @@ trait SourceBundle { type T = M def applyLens[V](lens : BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState]) : LVar[V] = { - val newVar = new LVar[V](Signal{lens.toView(internal.value)}, ListBuffer.empty[Event[V]]) + val newVar = new LVar[V](Signal{lens.toView(internal.value)}, ListBuffer[Event[V]](Evt[V]())) events += newVar.getEvent().map(e => lens.toModel(e)) return newVar } @@ -140,7 +140,7 @@ trait SourceBundle { object LVar { def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = { - val events = ListBuffer.empty[Event[T]] + val events = ListBuffer[Event[T]](Evt[T]()) new LVar[T](events.reduce { (a, b) => a || b }.hold(init = initval), events) } From fba55ae25392195364f97cb520166c25be57653d Mon Sep 17 00:00:00 2001 From: Josef552 <105931716+Josef552@users.noreply.github.com> Date: Wed, 17 Jan 2024 15:39:27 +0100 Subject: [PATCH 17/33] a --- .../src/main/scala/copl/ConversionTest.scala | 99 +++++++++++-------- .../scala/rescala/operator/SourceBundle.scala | 27 ++++- 2 files changed, 84 insertions(+), 42 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index a38cb44d1..abc51b049 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -1,6 +1,6 @@ package copl -import org.scalajs.dom.html.{Input, Paragraph} +import org.scalajs.dom.html.Input import org.scalajs.dom.document import rescala.extra.Tags import rescala.interfaces.toposort @@ -12,7 +12,7 @@ import scalatags.JsDom.TypedTag import scala.scalajs.js.annotation.JSExportTopLevel object TopoTags extends Tags[rescala.interfaces.toposort.type](rescala.interfaces.toposort, true) -import TopoTags.* +//import TopoTags.* object ConversionTest { @@ -21,7 +21,7 @@ object ConversionTest { def main(args: Array[String]): Unit = { //signalTest() - val temperatureConverter = toStringConverter() + val temperatureConverter = testSignalLens() document.body.replaceChild(temperatureConverter.render, document.body.firstChild) () } @@ -70,36 +70,57 @@ object ConversionTest { } - def getOneWayConverter() = { - - val meterInput: TypedTag[Input] = input(placeholder := "Meters") - val (meterEvent: Event[String], renderedMeter: Input) = RenderUtil.inputFieldHandler(meterInput, oninput, clear = false) - - val yardSignal: Signal[Option[Double]] = meterEvent.hold(init="Please enter a valid value for meters.").map { str => convertMeterToYard(str.toDoubleOption)} - - val yardParagraph: Signal[TypedTag[Paragraph]] = yardSignal.map { yard => p(if (yard.isEmpty) "Please enter a valid value for meters." else yard.get.toString)} - - div(p("One way conversion using Signal"), renderedMeter, yardParagraph.asModifier) +// def getOneWayConverter() = { +// +// val meterInput: TypedTag[Input] = input(placeholder := "Meters") +// val (meterEvent: Event[String], renderedMeter: Input) = RenderUtil.inputFieldHandler(meterInput, oninput, clear = false) +// +// val yardSignal: Signal[Option[Double]] = meterEvent.hold(init="Please enter a valid value for meters.").map { str => convertMeterToYard(str.toDoubleOption)} +// +// val yardParagraph: Signal[TypedTag[Paragraph]] = yardSignal.map { yard => p(if (yard.isEmpty) "Please enter a valid value for meters." else yard.get.toString)} +// +// div(p("One way conversion using Signal"), renderedMeter, yardParagraph.asModifier) +// } + +// def getTemperatureConverter() = { +// +// val celsiusVar = LVar(0.0) +// val kelvinVar = celsiusVar.applyLens(new AddLens(273.15)) +// +// val celsiusInput: TypedTag[Input] = input(value := celsiusVar.now) +// val (celsiusEvent: Event[String], renderedCelsius: Input) = RenderUtil.inputFieldHandler(celsiusInput, oninput, clear = false) +// +// val kelvinInput: TypedTag[Input] = input(value := kelvinVar.now) +// val (kelvinEvent: Event[String], renderedKelvin: Input) = RenderUtil.inputFieldHandler(kelvinInput, oninput, clear = false) +// +// celsiusEvent.observe{ str => celsiusVar.set(str.toDouble); renderedKelvin.value = kelvinVar.now.toString } +// kelvinEvent.observe { str => kelvinVar.set(str.toDouble); renderedCelsius.value = celsiusVar.now.toString } +// +// div(p("Unit Conversion with Lenses :D"), renderedCelsius, renderedKelvin) +// } + + def testSignalLens() = { + val leftVar = LVar(0.0) + val summand = Var{3.0} + val lensSig = Signal{ new AddLens(summand.value) } + val rightVar = leftVar.applyLens(new BijectiveSigLens(lensSig)) + + val leftInput: TypedTag[Input] = input(value := leftVar.now) + val (leftEvent: Event[String], renderedLeft: Input) = RenderUtil.inputFieldHandler(leftInput, oninput, clear = false) + + val sigInput: TypedTag[Input] = input(value := summand.now) + val (sigEvent: Event[String], renderedSig: Input) = RenderUtil.inputFieldHandler(sigInput, oninput, clear = false) + + val rightInput: TypedTag[Input] = input(value := rightVar.now) + val (rightEvent: Event[String], renderedRight: Input) = RenderUtil.inputFieldHandler(rightInput, oninput, clear = false) + + leftEvent.observe { str => leftVar.set(str.toDouble);renderedSig.value = summand.now.toString; renderedRight.value = rightVar.now.toString } + sigEvent.observe { str => summand.set(str.toDouble); renderedRight.value = rightVar.now.toString; renderedLeft.value = leftVar.now.toString } + rightEvent.observe { str => rightVar.set(str.toDouble); renderedSig.value = summand.now.toString; renderedLeft.value = leftVar.now.toString } + + div(p("Unit Conversion with Lenses :D"), renderedLeft, renderedSig, renderedRight) } - def getTemperatureConverter() = { - - val celsiusVar = LVar(0.0) - val kelvinVar = celsiusVar.applyLens(new AddLens(273.15)) - - val celsiusInput: TypedTag[Input] = input(value := celsiusVar.now) - val (celsiusEvent: Event[String], renderedCelsius: Input) = RenderUtil.inputFieldHandler(celsiusInput, oninput, clear = false) - - val kelvinInput: TypedTag[Input] = input(value := kelvinVar.now) - val (kelvinEvent: Event[String], renderedKelvin: Input) = RenderUtil.inputFieldHandler(kelvinInput, oninput, clear = false) - - celsiusEvent.observe{ str => celsiusVar.set(str.toDouble); renderedKelvin.value = kelvinVar.now.toString } - kelvinEvent.observe { str => kelvinVar.set(str.toDouble); renderedCelsius.value = celsiusVar.now.toString } - - div(p("Unit Conversion with Lenses :D"), renderedCelsius, renderedKelvin) - } - - def toStringConverter() = { val intVar = LVar(0) @@ -116,13 +137,13 @@ object ConversionTest { div(p("Unit Conversion with Lenses :D"), renderedInt, renderedStr) } - - - def convertMeterToYard(meter : Option[Double]): Option[Double] = { - if(meter.isEmpty) - Option.empty[Double] - else - Option[Double](meter.get * 0.9144) - } +// +// +// def convertMeterToYard(meter : Option[Double]): Option[Double] = { +// if(meter.isEmpty) +// Option.empty[Double] +// else +// Option[Double](meter.get * 0.9144) +// } } diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index 3fa5187fc..786eae49a 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -3,6 +3,7 @@ package rescala.operator import rescala.core.{AdmissionTicket, Base, CreationTicket, InitialChange, Observation, ReInfo, ReSource, Scheduler, ScopeSearch} import rescala.structure.Pulse +//import scala.language.implicitConversions import scala.util.Random trait SourceBundle { @@ -109,16 +110,24 @@ trait SourceBundle { } } - class LVar[M] private[rescala](model : LVar[?], lens: BijectiveLens[model.T , M], internal : Signal[M]) { + class LVar[M] private[rescala](model : LVar[?], lens: BijectiveSigLens[model.T, M], internal : Signal[M]) { type T = M + def set(value: M)(implicit sched: Scheduler[internal.State]): Unit = { model.set(lens.toModel(value)) } - def applyLens[V](lens : BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState]) : LVar[V] = { - new LVar[V](this, lens, Signal{lens.toView(internal.value)}) + def applyLens[V](lens: BijectiveSigLens[M, V])(implicit ticket: CreationTicket[BundleState]): LVar[V] = { + new LVar[V](this, lens, Signal.dynamic { + lens.toView(internal.value).value + }) } + def applyLens[V](lens: BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState]): LVar[V] = { + new LVar[V](this, lens, Signal.dynamic { + lens.toView(internal.value) + }) + } def now(implicit sched: Scheduler[internal.State]) : M = internal.now //How to make value accessible to the outside? @@ -196,8 +205,20 @@ trait SourceBundle { def toView(m: M): V def toModel(v: V): M def toModel(v: V, m: M): M = toModel(v) + +// implicit def toSigLens(lens: BijectiveLens[M, V]): BijectiveSigLens[M, V] = new BijectiveSigLens(Signal { +// lens +// }) + } + + class BijectiveSigLens[M, V](lensSig : Signal[BijectiveLens[M, V]])(implicit sched: Scheduler[lensSig.State]){ + def toView(m: M) : Signal[V] = Signal{ lensSig.value.toView(m)} + def toModel(v: V) : M = lensSig.now.toModel(v) + def toModel(v: V, m: M): M = toModel(v) } + //given Conversion[BijectiveLens[M, V], BijectiveSigLens[M, V]] = new BijectiveSigLens(Signal{ _ }) + class AddLens[A](k: A)(implicit num: Numeric[A]) extends BijectiveLens[A, A] { def toView(m: A): A = num.plus(m, k) def toModel(v: A): A = num.minus(v, k) From c797e7863fc2dc86ee964b9443f04b29bd39898a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Wed, 17 Jan 2024 15:57:19 +0100 Subject: [PATCH 18/33] Fixed Signal Lenses --- .../src/main/scala/copl/ConversionTest.scala | 40 +++++++++---------- .../scala/rescala/operator/SourceBundle.scala | 12 ++---- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index abc51b049..24e3268b6 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -1,6 +1,6 @@ package copl -import org.scalajs.dom.html.Input +import org.scalajs.dom.html.{Input, Paragraph} import org.scalajs.dom.document import rescala.extra.Tags import rescala.interfaces.toposort @@ -12,7 +12,7 @@ import scalatags.JsDom.TypedTag import scala.scalajs.js.annotation.JSExportTopLevel object TopoTags extends Tags[rescala.interfaces.toposort.type](rescala.interfaces.toposort, true) -//import TopoTags.* +import TopoTags.* object ConversionTest { @@ -21,7 +21,7 @@ object ConversionTest { def main(args: Array[String]): Unit = { //signalTest() - val temperatureConverter = testSignalLens() + val temperatureConverter = toStringConverter() document.body.replaceChild(temperatureConverter.render, document.body.firstChild) () } @@ -70,17 +70,17 @@ object ConversionTest { } -// def getOneWayConverter() = { -// -// val meterInput: TypedTag[Input] = input(placeholder := "Meters") -// val (meterEvent: Event[String], renderedMeter: Input) = RenderUtil.inputFieldHandler(meterInput, oninput, clear = false) -// -// val yardSignal: Signal[Option[Double]] = meterEvent.hold(init="Please enter a valid value for meters.").map { str => convertMeterToYard(str.toDoubleOption)} -// -// val yardParagraph: Signal[TypedTag[Paragraph]] = yardSignal.map { yard => p(if (yard.isEmpty) "Please enter a valid value for meters." else yard.get.toString)} -// -// div(p("One way conversion using Signal"), renderedMeter, yardParagraph.asModifier) -// } + def getOneWayConverter() = { + + val meterInput: TypedTag[Input] = input(placeholder := "Meters") + val (meterEvent: Event[String], renderedMeter: Input) = RenderUtil.inputFieldHandler(meterInput, oninput, clear = false) + + val yardSignal: Signal[Option[Double]] = meterEvent.hold(init="Please enter a valid value for meters.").map { str => convertMeterToYard(str.toDoubleOption)} + + val yardParagraph: Signal[TypedTag[Paragraph]] = yardSignal.map { yard => p(if (yard.isEmpty) "Please enter a valid value for meters." else yard.get.toString)} + + div(p("One way conversion using Signal"), renderedMeter, yardParagraph.asModifier) + } // def getTemperatureConverter() = { // @@ -139,11 +139,11 @@ object ConversionTest { } // // -// def convertMeterToYard(meter : Option[Double]): Option[Double] = { -// if(meter.isEmpty) -// Option.empty[Double] -// else -// Option[Double](meter.get * 0.9144) -// } + def convertMeterToYard(meter : Option[Double]): Option[Double] = { + if(meter.isEmpty) + Option.empty[Double] + else + Option[Double](meter.get * 0.9144) + } } diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index 786eae49a..918045029 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -112,21 +112,17 @@ trait SourceBundle { class LVar[M] private[rescala](model : LVar[?], lens: BijectiveSigLens[model.T, M], internal : Signal[M]) { type T = M - + def set(value: M)(implicit sched: Scheduler[internal.State]): Unit = { model.set(lens.toModel(value)) } def applyLens[V](lens: BijectiveSigLens[M, V])(implicit ticket: CreationTicket[BundleState]): LVar[V] = { - new LVar[V](this, lens, Signal.dynamic { - lens.toView(internal.value).value - }) + new LVar[V](this, lens, Signal.dynamic{lens.toView(internal.value).value}) } - def applyLens[V](lens: BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState]): LVar[V] = { - new LVar[V](this, lens, Signal.dynamic { - lens.toView(internal.value) - }) + def applyLens[V](lens: BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[internal.State]): LVar[V] = { + new LVar[V](this, new BijectiveSigLens(Signal{lens}), Signal.dynamic {lens.toView(internal.value)}) } def now(implicit sched: Scheduler[internal.State]) : M = internal.now From 5edb9e0ba96abfe2e536da8452616eae42f5a69b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Wed, 17 Jan 2024 19:03:35 +0100 Subject: [PATCH 19/33] Added Single Transaction Event-Based Propagation --- .../scala/rescala/operator/SourceBundle.scala | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index 49dccfc89..c608c4c79 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -4,7 +4,6 @@ import rescala.core.{AdmissionTicket, Base, CreationTicket, InitialChange, Obser import rescala.structure.Pulse import scala.util.Random -import scala.collection.mutable.ListBuffer trait SourceBundle { self: Operators => @@ -110,25 +109,25 @@ trait SourceBundle { } } - class LVar[M] private[rescala](internal : Signal[M], events : ListBuffer[Event[M]]) { + class LVar[M] private[rescala](val internal : Signal[M], events : Evt[Event[M]]) { type T = M - def applyLens[V](lens : BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState]) : LVar[V] = { - val newVar = new LVar[V](Signal{lens.toView(internal.value)}, ListBuffer[Event[V]](Evt[V]())) - events += newVar.getEvent().map(e => lens.toModel(e)) + def applyLens[V](lens : BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]) : LVar[V] = { + val newVar = new LVar[V](Signal{lens.toView(internal.value)}, Evt()) + events.fire(newVar.getEvent().map{e => lens.toModel(e)}) return newVar } - def observe(e: Event[M])(implicit ticket: CreationTicket[BundleState]) : Unit = { - events += e + def observe(e: Event[M])(implicit sched: Scheduler[BundleState]) : Unit = { + events.fire(e) } - def getEvent() (implicit ticket: CreationTicket[BundleState]) : Event[M] = events.reduce { (a, b) => a || b } + def getEvent()(implicit ticket: CreationTicket[BundleState]) : Event[M] = events.list().flatten(firstFiringEvent)//.map(_.flatten.head) - def now(implicit sched: Scheduler[internal.State]) : M = internal.now + def now(implicit sched: Scheduler[BundleState]) : M = internal.now //How to make value accessible to the outside? - inline def value(implicit sched: Scheduler[internal.State]) : M = internal.value + inline def value(implicit sched: Scheduler[BundleState]) : M = internal.value } @@ -140,8 +139,8 @@ trait SourceBundle { object LVar { def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = { - val events = ListBuffer[Event[T]](Evt[T]()) - new LVar[T](events.reduce { (a, b) => a || b }.hold(init = initval), events) + val events : Evt[Event[T]] = Evt() + new LVar[T](events.list().flatten(firstFiringEvent).hold(initval), events) } // def empty[T](implicit ticket: CreationTicket[BundleState]): LVar[T] = { From 98184b9267499b94fb90101d90371f641c11b19e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Thu, 18 Jan 2024 11:33:35 +0100 Subject: [PATCH 20/33] Fixed merge issues --- .../src/main/scala/copl/ConversionTest.scala | 84 +++++++++++-------- .../src/main/scala/copl/RenderUtil.scala | 2 +- .../scala/rescala/operator/SourceBundle.scala | 28 ++----- 3 files changed, 57 insertions(+), 57 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index a599362ca..57bbcc163 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -1,7 +1,7 @@ package copl import org.scalajs.dom.html.{Input, Paragraph} -//import org.scalajs.dom.document +import org.scalajs.dom.document import rescala.extra.Tags import rescala.interfaces.toposort import rescala.interfaces.toposort.* @@ -20,12 +20,15 @@ object ConversionTest { def run(): Unit = main(Array.empty[String]) def main(args: Array[String]): Unit = { - signalTest() -// val temperatureConverter = getTemperatureConverter() -// document.body.replaceChild(temperatureConverter.render, document.body.firstChild) +// signalTest() + val temperatureConverter = testSignalLens() + document.body.replaceChild(temperatureConverter.render, document.body.firstChild) () } + //TODO: Non-determenistic lens behaviour problematic due to simplified propagation + //TODO: Exceptions "disconnect" fields + def signalTest() = { @@ -35,9 +38,9 @@ object ConversionTest { val a = LVar(2) val b = a.applyLens(new AddLens(10)) val c = a.applyLens(new AddLens(-1)) - a.observe(evA) - b.observe(evB) - c.observe(evC) + a.fire(evA) + b.fire(evB) + c.fire(evC) a.getEvent().observe{value => println("Value of a changed to " + value)} b.getEvent().observe{value => println("Value of b changed to " + value)} c.getEvent().observe{value => println("Value of c changed to " + value)} @@ -79,47 +82,52 @@ object ConversionTest { div(p("One way conversion using Signal"), renderedMeter, yardParagraph.asModifier) } -// def getTemperatureConverter() = { -// -// val celsiusVar = LVar(0.0) -// val kelvinVar = celsiusVar.applyLens(new AddLens(273.15)) -// -// val celsiusInput: TypedTag[Input] = input(value := celsiusVar.now) -// val (celsiusEvent: Event[String], renderedCelsius: Input) = RenderUtil.inputFieldHandler(celsiusInput, oninput, clear = false) -// -// val kelvinInput: TypedTag[Input] = input(value := kelvinVar.now) -// val (kelvinEvent: Event[String], renderedKelvin: Input) = RenderUtil.inputFieldHandler(kelvinInput, oninput, clear = false) -// -// celsiusEvent.observe{ str => celsiusVar.set(str.toDouble); renderedKelvin.value = kelvinVar.now.toString } -// kelvinEvent.observe { str => kelvinVar.set(str.toDouble); renderedCelsius.value = celsiusVar.now.toString } -// celsiusVar.observe(celsiusEvent.map { str => str.toDouble }) -// kelvinVar.observe(kelvinEvent.map { str => str.toDouble }) -// -// div(p("Unit Conversion with Lenses :D"), renderedCelsius, renderedKelvin) -// } + def getTemperatureConverter() = { + + val celsiusVar = LVar(0.0) + val kelvinVar = celsiusVar.applyLens(new AddLens(273.15)) + + val celsiusInput: TypedTag[Input] = input(value := celsiusVar.now) + val (celsiusEvent: Event[String], renderedCelsius: Input) = RenderUtil.inputFieldHandler(celsiusInput, oninput, clear = false) + + val kelvinInput: TypedTag[Input] = input(value := kelvinVar.now) + val (kelvinEvent: Event[String], renderedKelvin: Input) = RenderUtil.inputFieldHandler(kelvinInput, oninput, clear = false) + + celsiusVar.fire(celsiusEvent.map {str => if (str.toDoubleOption.isDefined) str.toDouble else 0.0 }) + kelvinVar.fire(kelvinEvent.map {str => if (str.toDoubleOption.isDefined) str.toDouble else 0.0 }) + + kelvinVar.observe{value => ;renderedKelvin.value = value.toString} + celsiusVar.observe{value => ;renderedCelsius.value = value.toString} + + div(p("Unit Conversion with Lenses :D"), renderedCelsius, renderedKelvin) + } def testSignalLens() = { val leftVar = LVar(0.0) - val summand = Var{3.0} - val lensSig = Signal{ new AddLens(summand.value) } + + val sigInput: TypedTag[Input] = input(value := 3.0) + val (sigEvent: Event[String], renderedSig: Input) = RenderUtil.inputFieldHandler(sigInput, oninput, clear = false) + + val lensSig = Signal{ new AddLens(sigEvent.hold(init = renderedSig.value).map {(str : String) => (str.toDouble : Double)}.value)} + val rightVar = leftVar.applyLens(new BijectiveSigLens(lensSig)) val leftInput: TypedTag[Input] = input(value := leftVar.now) val (leftEvent: Event[String], renderedLeft: Input) = RenderUtil.inputFieldHandler(leftInput, oninput, clear = false) - val sigInput: TypedTag[Input] = input(value := summand.now) - val (sigEvent: Event[String], renderedSig: Input) = RenderUtil.inputFieldHandler(sigInput, oninput, clear = false) - val rightInput: TypedTag[Input] = input(value := rightVar.now) val (rightEvent: Event[String], renderedRight: Input) = RenderUtil.inputFieldHandler(rightInput, oninput, clear = false) - leftEvent.observe { str => leftVar.set(str.toDouble);renderedSig.value = summand.now.toString; renderedRight.value = rightVar.now.toString } - sigEvent.observe { str => summand.set(str.toDouble); renderedRight.value = rightVar.now.toString; renderedLeft.value = leftVar.now.toString } - rightEvent.observe { str => rightVar.set(str.toDouble); renderedSig.value = summand.now.toString; renderedLeft.value = leftVar.now.toString } + leftVar.fire(leftEvent.map(str => str.toDouble)) + rightVar.fire(rightEvent.map(str => str.toDouble)) + + leftVar.observe{value =>; renderedLeft.value = value.toString} + rightVar.observe{value =>; renderedRight.value = value.toString} div(p("Unit Conversion with Lenses :D"), renderedLeft, renderedSig, renderedRight) } + def toStringConverter() = { val intVar = LVar(0) @@ -131,13 +139,15 @@ object ConversionTest { val strInput: TypedTag[Input] = input(value := strVar.now) val (strEvent: Event[String], renderedStr: Input) = RenderUtil.inputFieldHandler(strInput, oninput, clear = false) -// intEvent.observe { str => intVar.set(str.toInt); renderedStr.value = strVar.now } -// strEvent.observe { str => strVar.set(str); renderedInt.value = intVar.now.toString } + intVar.fire(intEvent.map{_.toInt}) + strVar.fire(strEvent) + + intVar.observe{value => ; renderedInt.value = value.toString} + strVar.observe{value => ; renderedStr.value = value} div(p("Unit Conversion with Lenses :D"), renderedInt, renderedStr) } -// -// + def convertMeterToYard(meter : Option[Double]): Option[Double] = { if(meter.isEmpty) Option.empty[Double] diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala index 8416b096b..d64e44942 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala @@ -7,7 +7,7 @@ import scalatags.JsDom.all.* import scalatags.JsDom.{Attr, TypedTag} object RenderUtil { - def inputFieldHandler(tag: TypedTag[Input], attr: Attr, clear: Boolean = true): (Event[String], Input) = { + def inputFieldHandler(tag: TypedTag[Input], attr: Attr, clear: Boolean = true): (Event[String], Input) = { val handler = Event.fromCallback(tag(attr := Event.handle[UIEvent])) val todoInputField: Input = handler.data.render diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index 7d015a581..fe7eb4402 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -119,31 +119,25 @@ trait SourceBundle { return newVar } - def observe(e: Event[M])(implicit sched: Scheduler[BundleState]) : Unit = { - events.fire(e) + def applyLens[V](lens: BijectiveSigLens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]): LVar[V] = { + val newVar = new LVar[V](Signal{lens.toView(internal.value)}.flatten, Evt()) + events.fire(newVar.getEvent().map { e => lens.toModel(e) }) + return newVar } + def fire(e: Event[M])(implicit sched: Scheduler[BundleState]) : Unit = events.fire(e) + + def observe(onValue: M => Unit, onError: Throwable => Unit = null, fireImmediately: Boolean = false) + (implicit ticket: CreationTicket[BundleState]) = internal.observe(onValue, onError, fireImmediately) + def getEvent()(implicit ticket: CreationTicket[BundleState]) : Event[M] = events.list().flatten(firstFiringEvent)//.map(_.flatten.head) -// def applyLens[V](lens: BijectiveSigLens[M, V])(implicit ticket: CreationTicket[BundleState]): LVar[V] = { -// new LVar[V](this, lens, Signal.dynamic{lens.toView(internal.value).value}) -// } -// -// def applyLens[V](lens: BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[internal.State]): LVar[V] = { -// new LVar[V](this, new BijectiveSigLens(Signal{lens}), Signal.dynamic {lens.toView(internal.value)}) -// } def now(implicit sched: Scheduler[internal.State]) : M = internal.now - //How to make value accessible to the outside? inline def value(implicit sched: Scheduler[BundleState]) : M = internal.value } -// class RootLVar[M] private[rescala](initVal : M, var event: Event[M]) (implicit ticket: CreationTicket[BundleState]) -// extends LVar[M] (event.hold(init = initVal), event) { -// -// } - object LVar { def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = { @@ -151,10 +145,6 @@ trait SourceBundle { new LVar[T](events.list().flatten(firstFiringEvent).hold(initval), events) } -// def empty[T](implicit ticket: CreationTicket[BundleState]): LVar[T] = { -// new RootLVar[T](Var.empty) -// } - } trait Lens[M, V] { From 58d53f53ff3726478ccf10163a5b9db4c3628244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Wed, 24 Jan 2024 20:25:05 +0100 Subject: [PATCH 21/33] Updated to enable basic lenses --- .../src/main/scala/copl/ConversionTest.scala | 4 +-- .../scala/rescala/operator/SourceBundle.scala | 28 +++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 57bbcc163..434528b5c 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -21,7 +21,7 @@ object ConversionTest { def main(args: Array[String]): Unit = { // signalTest() - val temperatureConverter = testSignalLens() + val temperatureConverter = toStringConverter() document.body.replaceChild(temperatureConverter.render, document.body.firstChild) () } @@ -131,7 +131,7 @@ object ConversionTest { def toStringConverter() = { val intVar = LVar(0) - val strVar = intVar.applyLens(new CharCountLens()) + val strVar = intVar.applyLens(new NonDeterministicCharCountLens()) val intInput: TypedTag[Input] = input(value := intVar.now) val (intEvent: Event[String], renderedInt: Input) = RenderUtil.inputFieldHandler(intInput, oninput, clear = false) diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index fe7eb4402..c1f17fdac 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -110,18 +110,34 @@ trait SourceBundle { } } +/* +On Determinism: +Due to the chosen propagation strategy all lens projections must be deterministic, as non-deterministic behaviour may override user Inputs, +e.g. see the CharLens. There are two approaches two fix this issue. The first is an update the propagation mechanism which would need to be +developed from scratch as the current implementation cannot allow for the exclusion of the original path. The second way is to include the View +state in the lens, i.e. change the function signature to toView(m: M, v : V). Then, in the example of the CharLens, the check v.length == m +could prevent an update, though this might not always be the intended behaviour (i.e. a button to regenerate password should give a new password, +even if the length did not change). + +On State in Lenses: +To me, the inclusion of the model state in the default lens seems to be unnecessary. In my opinion, the state should be included in the LVar instead, +where a fold on getEvent() should allow for the same functionality AFIK. This separates functionality more cleanly on the rescala transaction side +and reduces the complexity of the Lenses, by reducing them to their "pure" form of a simple bijective mapping. + */ + class LVar[M] private[rescala](val internal : Signal[M], events : Evt[Event[M]]) { type T = M - def applyLens[V](lens : BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]) : LVar[V] = { + def applyLens[V](lens : Lens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]) : LVar[V] = { val newVar = new LVar[V](Signal{lens.toView(internal.value)}, Evt()) - events.fire(newVar.getEvent().map{e => lens.toModel(e)}) + events.fire(newVar.getEvent().map{ e => lens.toModel(e, internal.now) }) return newVar } + //TODO make not bijective def applyLens[V](lens: BijectiveSigLens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]): LVar[V] = { val newVar = new LVar[V](Signal{lens.toView(internal.value)}.flatten, Evt()) - events.fire(newVar.getEvent().map { e => lens.toModel(e) }) + events.fire(newVar.getEvent().map { e => lens.toModel(e, internal.now) }) return newVar } @@ -156,10 +172,6 @@ trait SourceBundle { def toView(m: M): V def toModel(v: V): M def toModel(v: V, m: M): M = toModel(v) - -// implicit def toSigLens(lens: BijectiveLens[M, V]): BijectiveSigLens[M, V] = new BijectiveSigLens(Signal { -// lens -// }) } class BijectiveSigLens[M, V](lensSig : Signal[BijectiveLens[M, V]])(implicit sched: Scheduler[lensSig.State]){ @@ -175,7 +187,7 @@ trait SourceBundle { def toModel(v: A): A = num.minus(v, k) } - class CharCountLens extends BijectiveLens[Int, String] { + class NonDeterministicCharCountLens extends BijectiveLens[Int, String] { // Lens that converts an int to a sequence of chars, eg. 5 => "aaaaa" def toView(m: Int): String = Random.alphanumeric.take(m).mkString def toModel(v: String): Int = v.length From 0d14f21749969e12a955bcd4a3d42d48b5b1dc5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Thu, 25 Jan 2024 15:06:22 +0100 Subject: [PATCH 22/33] Refactoring, Clean-up & Documentation --- .../src/main/scala/copl/ConversionTest.scala | 2 +- .../scala/rescala/operator/SourceBundle.scala | 86 +++++++++++-------- 2 files changed, 52 insertions(+), 36 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 434528b5c..5898970c9 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -21,7 +21,7 @@ object ConversionTest { def main(args: Array[String]): Unit = { // signalTest() - val temperatureConverter = toStringConverter() + val temperatureConverter = getTemperatureConverter() document.body.replaceChild(temperatureConverter.render, document.body.firstChild) () } diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index c1f17fdac..90f8d603c 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -110,52 +110,56 @@ trait SourceBundle { } } -/* -On Determinism: -Due to the chosen propagation strategy all lens projections must be deterministic, as non-deterministic behaviour may override user Inputs, -e.g. see the CharLens. There are two approaches two fix this issue. The first is an update the propagation mechanism which would need to be -developed from scratch as the current implementation cannot allow for the exclusion of the original path. The second way is to include the View -state in the lens, i.e. change the function signature to toView(m: M, v : V). Then, in the example of the CharLens, the check v.length == m -could prevent an update, though this might not always be the intended behaviour (i.e. a button to regenerate password should give a new password, -even if the length did not change). - -On State in Lenses: -To me, the inclusion of the model state in the default lens seems to be unnecessary. In my opinion, the state should be included in the LVar instead, -where a fold on getEvent() should allow for the same functionality AFIK. This separates functionality more cleanly on the rescala transaction side -and reduces the complexity of the Lenses, by reducing them to their "pure" form of a simple bijective mapping. - */ - - class LVar[M] private[rescala](val internal : Signal[M], events : Evt[Event[M]]) { - type T = M - - def applyLens[V](lens : Lens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]) : LVar[V] = { - val newVar = new LVar[V](Signal{lens.toView(internal.value)}, Evt()) - events.fire(newVar.getEvent().map{ e => lens.toModel(e, internal.now) }) - return newVar - } - - //TODO make not bijective + /** + * LVars serve as the basis for reactive lenses. TODO: Link Documentation + * @param state The state of the LVar + * @param events TODO How to explain this? + */ + class LVar[M] private[rescala](state : Signal[M], events : Evt[Event[M]]) { + + /** + * Creates a new LVar which is connected to this LVar via the given Lens. + * @param lens The lens which connects the LVars. Can use implicit conversion from BijectiveLens if the Lens does not need to change later + */ def applyLens[V](lens: BijectiveSigLens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]): LVar[V] = { - val newVar = new LVar[V](Signal{lens.toView(internal.value)}.flatten, Evt()) - events.fire(newVar.getEvent().map { e => lens.toModel(e, internal.now) }) + val newVar = new LVar[V](state.map { model => lens.toView(model)}.flatten, Evt()) + events.fire(newVar.getEvent().map { e => lens.toModel(e) }) return newVar } + /** + * Register an event which should trigger a change of this LVar (and consequently the entire lens cluster) + * @param e The event which should change the LVar + */ def fire(e: Event[M])(implicit sched: Scheduler[BundleState]) : Unit = events.fire(e) + /** + * Function to observe a change of the LVar. Simple wrapper for internal.observe + */ def observe(onValue: M => Unit, onError: Throwable => Unit = null, fireImmediately: Boolean = false) - (implicit ticket: CreationTicket[BundleState]) = internal.observe(onValue, onError, fireImmediately) + (implicit ticket: CreationTicket[BundleState]) = state.observe(onValue, onError, fireImmediately) - def getEvent()(implicit ticket: CreationTicket[BundleState]) : Event[M] = events.list().flatten(firstFiringEvent)//.map(_.flatten.head) + //TODO Documentation + def getEvent()(implicit ticket: CreationTicket[BundleState]) : Event[M] = events.list().flatten(firstFiringEvent) - def now(implicit sched: Scheduler[internal.State]) : M = internal.now + /** + * Function to access the current value of the lens. Simple wrapper for internal.now + */ + def now(implicit sched: Scheduler[BundleState]) : M = state.now - inline def value(implicit sched: Scheduler[BundleState]) : M = internal.value + /** + * Function to access state of LVar in reactives. Simple wrapper for internal.value. + */ + inline def value(implicit sched: Scheduler[BundleState]) : M = state.value } object LVar { + /** + * Creates a new LVar with the given initial value. This will be the root of a new Lens cluster + * @param initval the inital value of the LVar + */ def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = { val events : Evt[Event[T]] = Evt() new LVar[T](events.list().flatten(firstFiringEvent).hold(initval), events) @@ -163,6 +167,7 @@ and reduces the complexity of the Lenses, by reducing them to their "pure" form } + //TODO: Note that non-bijective lenses are not supported due to the chosen propagation. Only keep for demonstration trait Lens[M, V] { def toView(m: M): V def toModel(v: V, m: M): M @@ -174,19 +179,30 @@ and reduces the complexity of the Lenses, by reducing them to their "pure" form def toModel(v: V, m: M): M = toModel(v) } - class BijectiveSigLens[M, V](lensSig : Signal[BijectiveLens[M, V]])(implicit sched: Scheduler[lensSig.State]){ - def toView(m: M) : Signal[V] = Signal{ lensSig.value.toView(m)} + /** + * TODO: The BijectiveSigLens requires a reactive read without evaluating dependencies. As this is currently not supported by REScala, it uses _now_ instead! + * @param lensSig + * @tparam M + * @tparam V + */ + class BijectiveSigLens[M, V](lensSig : Signal[BijectiveLens[M, V]])(implicit sched: Scheduler[BundleState]){ + def toView(m: M) : Signal[V] = lensSig.map { model => model.toView(m) } def toModel(v: V) : M = lensSig.now.toModel(v) - def toModel(v: V, m: M): M = toModel(v) } - //given Conversion[BijectiveLens[M, V], BijectiveSigLens[M, V]] = new BijectiveSigLens(Signal{ _ }) + /** + * Implicit conversion of a BijectiveLens to a BijectiveSigLens for uniform handling. + * Could instead also be handled by method overloading (currently only LVar.applyLens(..)) for slightly more efficiency. + */ + implicit def toSignalLens[M, V](lens: BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState], + sched: Scheduler[BundleState]): BijectiveSigLens[M, V] = BijectiveSigLens(Signal {lens}) class AddLens[A](k: A)(implicit num: Numeric[A]) extends BijectiveLens[A, A] { def toView(m: A): A = num.plus(m, k) def toModel(v: A): A = num.minus(v, k) } + //IS NOT BIJECTIVE class NonDeterministicCharCountLens extends BijectiveLens[Int, String] { // Lens that converts an int to a sequence of chars, eg. 5 => "aaaaa" def toView(m: Int): String = Random.alphanumeric.take(m).mkString From 81f4617ba380a190ae8628b223802d258774c7e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Fri, 26 Jan 2024 13:46:41 +0100 Subject: [PATCH 23/33] Added Simple Calculator, inversion, compose --- .../src/main/scala/copl/ConversionTest.scala | 48 +++++++++++++++++-- .../src/main/scala/copl/RenderUtil.scala | 18 ++++++- .../scala/rescala/operator/SourceBundle.scala | 43 +++++++++++++++-- 3 files changed, 101 insertions(+), 8 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 5898970c9..8162c53c8 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -1,6 +1,6 @@ package copl -import org.scalajs.dom.html.{Input, Paragraph} +import org.scalajs.dom.html.{Input, Paragraph, Select} import org.scalajs.dom.document import rescala.extra.Tags import rescala.interfaces.toposort @@ -21,7 +21,7 @@ object ConversionTest { def main(args: Array[String]): Unit = { // signalTest() - val temperatureConverter = getTemperatureConverter() + val temperatureConverter = basicCalculator() document.body.replaceChild(temperatureConverter.render, document.body.firstChild) () } @@ -70,6 +70,46 @@ object ConversionTest { } + def basicCalculator() = { + val leftVar = LVar(0.0) + + //Handle operation dropdown + val operationInput: TypedTag[Select] = select(option("ADD"), option("SUB")) + val (operationEvent: Event[String], renderedOperation: Select) = RenderUtil.dropDownHandler(operationInput, oninput, clear = false) + val operationSignal: Signal[String] = operationEvent.hold(init = renderedOperation.value) + + //Handle value input + val sigInput: TypedTag[Input] = input(value := 0.0) + val (sigEvent: Event[String], renderedSig: Input) = RenderUtil.inputFieldHandler(sigInput, oninput, clear = false) + val valueSignal: Signal[Double] = sigEvent.hold(init = renderedSig.value).map { (str: String) => (str.toDouble: Double) } + + val lens : BijectiveSigLens[Double, Double] = new BijectiveSigLens[Double, Double](Signal{ + if (operationSignal.value == "ADD") { + new AddLens[Double](valueSignal.value) + } else if (operationSignal.value == "SUB") { + new AddLens[Double](valueSignal.value).inverse + } else { + new NeutralLens[Double] + } + }) + + val rightVar = leftVar.applyLens(lens) + + val leftInput: TypedTag[Input] = input(value := leftVar.now) + val (leftEvent: Event[String], renderedLeft: Input) = RenderUtil.inputFieldHandler(leftInput, oninput, clear = false) + + val rightInput: TypedTag[Input] = input(value := rightVar.now) + val (rightEvent: Event[String], renderedRight: Input) = RenderUtil.inputFieldHandler(rightInput, oninput, clear = false) + + leftVar.fire(leftEvent.map(str => str.toDouble)) + rightVar.fire(rightEvent.map(str => str.toDouble)) + + leftVar.observe { value => ; renderedLeft.value = value.toString } + rightVar.observe { value => ; renderedRight.value = value.toString } + + div(p("A simple Calculator"), renderedLeft, renderedOperation, renderedSig, renderedRight) + } + def getOneWayConverter() = { val meterInput: TypedTag[Input] = input(placeholder := "Meters") @@ -85,7 +125,7 @@ object ConversionTest { def getTemperatureConverter() = { val celsiusVar = LVar(0.0) - val kelvinVar = celsiusVar.applyLens(new AddLens(273.15)) + val kelvinVar = celsiusVar.applyLens(new AddLens(273.15).compose(new AddLens(273.15).inverse)) val celsiusInput: TypedTag[Input] = input(value := celsiusVar.now) val (celsiusEvent: Event[String], renderedCelsius: Input) = RenderUtil.inputFieldHandler(celsiusInput, oninput, clear = false) @@ -108,7 +148,7 @@ object ConversionTest { val sigInput: TypedTag[Input] = input(value := 3.0) val (sigEvent: Event[String], renderedSig: Input) = RenderUtil.inputFieldHandler(sigInput, oninput, clear = false) - val lensSig = Signal{ new AddLens(sigEvent.hold(init = renderedSig.value).map {(str : String) => (str.toDouble : Double)}.value)} + val lensSig = Signal{ new AddLens(sigEvent.hold(init = renderedSig.value).map {(str : String) => (str.toDouble : Double)}.value).inverse} val rightVar = leftVar.applyLens(new BijectiveSigLens(lensSig)) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala index d64e44942..83bbb1d6d 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala @@ -1,7 +1,7 @@ package copl import org.scalajs.dom.UIEvent -import org.scalajs.dom.html.Input +import org.scalajs.dom.html.{Input, Select} import rescala.interfaces.toposort.* import scalatags.JsDom.all.* import scalatags.JsDom.{Attr, TypedTag} @@ -22,4 +22,20 @@ object RenderUtil { (inputFieldText, todoInputField) } + + def dropDownHandler(tag: TypedTag[Select], attr: Attr, clear: Boolean = true): (Event[String], Select) = { + val handler = Event.fromCallback(tag(attr := Event.handle[UIEvent])) + val todoInputField: Select = handler.data.render + +// // observer to prevent form submit and empty content +// handler.event.observe { (e: UIEvent) => +// e.preventDefault() +// if (clear) todoInputField.value = "" +// } +// +// // note that the accessed value is NOT a reactive, there is a name clash with the JS library :-) + val inputFieldText = handler.event.map { _ => todoInputField.value.trim } + + (inputFieldText, todoInputField) + } } diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index 90f8d603c..f9388a9fb 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -121,8 +121,9 @@ trait SourceBundle { * Creates a new LVar which is connected to this LVar via the given Lens. * @param lens The lens which connects the LVars. Can use implicit conversion from BijectiveLens if the Lens does not need to change later */ + def applyLens[V](lens: BijectiveSigLens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]): LVar[V] = { - val newVar = new LVar[V](state.map { model => lens.toView(model)}.flatten, Evt()) + val newVar = new LVar[V](state.map { model => lens.toView(model) }.flatten, Evt()) events.fire(newVar.getEvent().map { e => lens.toModel(e) }) return newVar } @@ -173,10 +174,41 @@ trait SourceBundle { def toModel(v: V, m: M): M } - trait BijectiveLens[M, V] extends Lens[M, V] { + /** + * The base trait for all bijective lenses + * @tparam M The type of the model + * @tparam V The type of the view + */ + trait BijectiveLens[M, V] { + /** + * Transforms the model to the view + */ def toView(m: M): V + + /** + * Transforms the view to the model + */ def toModel(v: V): M - def toModel(v: V, m: M): M = toModel(v) + + //TODO: Better way to pass this than via implicit? + + /** + * Inverts the lens such that e.g. an AddLens functions like a SubLens. Note that this does not change the model-view relationship, + * i.e. the asymmetry is not inverted. + */ + def inverse(implicit lens: BijectiveLens[M, V] = this): BijectiveLens[V, M] = new BijectiveLens[V, M] { + override def toView(m: V): M = lens.toModel(m) + override def toModel(v: M): V = lens.toView(v) + } + + /** + * Concatenates this lens with another lens and returns the resulting lens. + * @param other The other lens + */ + def compose[W](other: BijectiveLens[V, W])(implicit lens: BijectiveLens[M, V] = this): BijectiveLens[M, W] = new BijectiveLens[M, W] { + override def toView(m: M) : W = other.toView(lens.toView(m)) + override def toModel(w: W): M = lens.toModel(other.toModel(w)) + } } /** @@ -202,6 +234,11 @@ trait SourceBundle { def toModel(v: A): A = num.minus(v, k) } + class NeutralLens[A] extends BijectiveLens[A, A] { + def toView(m: A): A = m + def toModel(v: A): A = v + } + //IS NOT BIJECTIVE class NonDeterministicCharCountLens extends BijectiveLens[Int, String] { // Lens that converts an int to a sequence of chars, eg. 5 => "aaaaa" From 0d06cd09226089cc55c5d617b467ff50952f6051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Sun, 28 Jan 2024 22:05:45 +0100 Subject: [PATCH 24/33] Working TemperatureConverter --- .../src/main/scala/copl/ConversionTest.scala | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 8162c53c8..69d23bb43 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -21,14 +21,18 @@ object ConversionTest { def main(args: Array[String]): Unit = { // signalTest() - val temperatureConverter = basicCalculator() + val temperatureConverter = getTemperatureConverter() document.body.replaceChild(temperatureConverter.render, document.body.firstChild) () } - //TODO: Non-determenistic lens behaviour problematic due to simplified propagation - //TODO: Exceptions "disconnect" fields + enum TempConversion(val lens: BijectiveLens[Double, Double]): + case C extends TempConversion(new NeutralLens) + case K extends TempConversion(new AddLens(274.15)) + case L extends TempConversion(new AddLens(253)) + end TempConversion + def conversionLens(from : TempConversion, to : TempConversion): BijectiveLens[Double, Double] = from.lens.inverse.compose(to.lens) def signalTest() = { @@ -124,22 +128,30 @@ object ConversionTest { def getTemperatureConverter() = { - val celsiusVar = LVar(0.0) - val kelvinVar = celsiusVar.applyLens(new AddLens(273.15).compose(new AddLens(273.15).inverse)) + val leftUnitInput: TypedTag[Select] = select(TempConversion.values.map{ unit => option(unit.toString) }) + val (leftUnitEvent: Event[String], renderedLeftUnit: Select) = RenderUtil.dropDownHandler(leftUnitInput, oninput, clear = false) + val leftUnitSignal: Signal[TempConversion] = leftUnitEvent.hold(init = renderedLeftUnit.value).map{TempConversion.valueOf(_)} - val celsiusInput: TypedTag[Input] = input(value := celsiusVar.now) - val (celsiusEvent: Event[String], renderedCelsius: Input) = RenderUtil.inputFieldHandler(celsiusInput, oninput, clear = false) + val rightUnitInput: TypedTag[Select] = select(TempConversion.values.map { unit => option(unit.toString) }) + val (rightUnitEvent: Event[String], renderedRightUnit: Select) = RenderUtil.dropDownHandler(rightUnitInput, oninput, clear = false) + val rightUnitSignal: Signal[TempConversion] = rightUnitEvent.hold(init = renderedRightUnit.value).map{TempConversion.valueOf(_)} - val kelvinInput: TypedTag[Input] = input(value := kelvinVar.now) - val (kelvinEvent: Event[String], renderedKelvin: Input) = RenderUtil.inputFieldHandler(kelvinInput, oninput, clear = false) + val leftVar = LVar(0.0) + val rightVar = leftVar.applyLens(BijectiveSigLens(Signal{conversionLens(leftUnitSignal.value, rightUnitSignal.value)})) + + val leftValueInput: TypedTag[Input] = input(value := leftVar.now) + val (leftValueEvent: Event[String], renderedLeftValue: Input) = RenderUtil.inputFieldHandler(leftValueInput, oninput, clear = false) - celsiusVar.fire(celsiusEvent.map {str => if (str.toDoubleOption.isDefined) str.toDouble else 0.0 }) - kelvinVar.fire(kelvinEvent.map {str => if (str.toDoubleOption.isDefined) str.toDouble else 0.0 }) + val rightValueInput: TypedTag[Input] = input(value := rightVar.now) + val (rightValueEvent: Event[String], renderedRightValue: Input) = RenderUtil.inputFieldHandler(rightValueInput, oninput, clear = false) - kelvinVar.observe{value => ;renderedKelvin.value = value.toString} - celsiusVar.observe{value => ;renderedCelsius.value = value.toString} + leftVar.fire(leftValueEvent.map {str => if (str.toDoubleOption.isDefined) str.toDouble else 0.0 }) + rightVar.fire(rightValueEvent.map {str => if (str.toDoubleOption.isDefined) str.toDouble else 0.0 }) - div(p("Unit Conversion with Lenses :D"), renderedCelsius, renderedKelvin) + rightVar.observe{value => ;renderedRightValue.value = value.toString} + leftVar.observe{value => ;renderedLeftValue.value = value.toString} + + div(p("Unit Conversion with Lenses"), renderedLeftUnit, renderedRightUnit, br , renderedLeftValue, renderedRightValue) } def testSignalLens() = { @@ -194,5 +206,4 @@ object ConversionTest { else Option[Double](meter.get * 0.9144) } - } From 60cad26b83df37b8ee67896d90bac25407d071c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Mon, 29 Jan 2024 21:38:09 +0100 Subject: [PATCH 25/33] MulLens --- .../src/main/scala/copl/ConversionTest.scala | 71 +++++++++++-------- .../scala/rescala/operator/SourceBundle.scala | 19 ++++- 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 8162c53c8..8b2595f00 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -9,6 +9,7 @@ import scalatags.JsDom import scalatags.JsDom.all.* import scalatags.JsDom.TypedTag +import scala.annotation.switch import scala.scalajs.js.annotation.JSExportTopLevel object TopoTags extends Tags[rescala.interfaces.toposort.type](rescala.interfaces.toposort, true) @@ -21,7 +22,7 @@ object ConversionTest { def main(args: Array[String]): Unit = { // signalTest() - val temperatureConverter = basicCalculator() + val temperatureConverter = unitConverter() document.body.replaceChild(temperatureConverter.render, document.body.firstChild) () } @@ -74,23 +75,23 @@ object ConversionTest { val leftVar = LVar(0.0) //Handle operation dropdown - val operationInput: TypedTag[Select] = select(option("ADD"), option("SUB")) + val operationInput: TypedTag[Select] = select(option("+"), option("-"), option("*"), option("/")) val (operationEvent: Event[String], renderedOperation: Select) = RenderUtil.dropDownHandler(operationInput, oninput, clear = false) val operationSignal: Signal[String] = operationEvent.hold(init = renderedOperation.value) //Handle value input - val sigInput: TypedTag[Input] = input(value := 0.0) - val (sigEvent: Event[String], renderedSig: Input) = RenderUtil.inputFieldHandler(sigInput, oninput, clear = false) - val valueSignal: Signal[Double] = sigEvent.hold(init = renderedSig.value).map { (str: String) => (str.toDouble: Double) } + val valueInput: TypedTag[Input] = input(value := 0.0) + val (valueEvent: Event[String], renderedValue: Input) = RenderUtil.inputFieldHandler(valueInput, oninput, clear = false) + val valueSignal: Signal[Double] = valueEvent.hold(init = renderedValue.value).map{toDoubleOr0(_)} val lens : BijectiveSigLens[Double, Double] = new BijectiveSigLens[Double, Double](Signal{ - if (operationSignal.value == "ADD") { - new AddLens[Double](valueSignal.value) - } else if (operationSignal.value == "SUB") { - new AddLens[Double](valueSignal.value).inverse - } else { - new NeutralLens[Double] - } + (operationSignal.value: @switch) match { + case "+" => new AddLens(valueSignal.value) + case "-" => new AddLens(valueSignal.value).inverse + case "*" => new MulLens(valueSignal.value) + case "/" => new MulLens(valueSignal.value).inverse + case _ => new NeutralLens + } }) val rightVar = leftVar.applyLens(lens) @@ -101,13 +102,14 @@ object ConversionTest { val rightInput: TypedTag[Input] = input(value := rightVar.now) val (rightEvent: Event[String], renderedRight: Input) = RenderUtil.inputFieldHandler(rightInput, oninput, clear = false) - leftVar.fire(leftEvent.map(str => str.toDouble)) - rightVar.fire(rightEvent.map(str => str.toDouble)) + leftVar.fire(leftEvent.map{toDoubleOr0(_)}) + rightVar.fire(rightEvent.map{toDoubleOr0(_)}) + //TODO This is ugly leftVar.observe { value => ; renderedLeft.value = value.toString } rightVar.observe { value => ; renderedRight.value = value.toString } - div(p("A simple Calculator"), renderedLeft, renderedOperation, renderedSig, renderedRight) + div(p("A simple Calculator"), renderedLeft, renderedOperation, renderedValue, " = ", renderedRight) } def getOneWayConverter() = { @@ -122,24 +124,25 @@ object ConversionTest { div(p("One way conversion using Signal"), renderedMeter, yardParagraph.asModifier) } - def getTemperatureConverter() = { - - val celsiusVar = LVar(0.0) - val kelvinVar = celsiusVar.applyLens(new AddLens(273.15).compose(new AddLens(273.15).inverse)) + def unitConverter() = { - val celsiusInput: TypedTag[Input] = input(value := celsiusVar.now) - val (celsiusEvent: Event[String], renderedCelsius: Input) = RenderUtil.inputFieldHandler(celsiusInput, oninput, clear = false) + val leftUnitInput: TypedTag[Select] = select(option("m"), option("km")) + val (leftUnitEvent: Event[String], renderedLeftUnit: Select) = RenderUtil.dropDownHandler(leftUnitInput, oninput, clear = false) + //val leftUnitSignal: Signal[String] = leftUnitEvent.hold(init = renderedLeftUnit.value) - val kelvinInput: TypedTag[Input] = input(value := kelvinVar.now) - val (kelvinEvent: Event[String], renderedKelvin: Input) = RenderUtil.inputFieldHandler(kelvinInput, oninput, clear = false) + val rightUnitInput: TypedTag[Select] = select(option("m"), option("km")) + val (rightUnitEvent: Event[String], renderedRightUnit: Select) = RenderUtil.dropDownHandler(rightUnitInput, oninput, clear = false) + //val rightUnitSignal: Signal[String] = rightUnitEvent.hold(init = renderedRightUnit.value) - celsiusVar.fire(celsiusEvent.map {str => if (str.toDoubleOption.isDefined) str.toDouble else 0.0 }) - kelvinVar.fire(kelvinEvent.map {str => if (str.toDoubleOption.isDefined) str.toDouble else 0.0 }) + val leftValueVar = LVar(1.0) + val leftValueInput: TypedTag[Input] = input(value := leftValueVar.now) + val (leftValueEvent: Event[String], renderedLeftValue: Input) = RenderUtil.inputFieldHandler(leftValueInput, oninput, clear = false) - kelvinVar.observe{value => ;renderedKelvin.value = value.toString} - celsiusVar.observe{value => ;renderedCelsius.value = value.toString} + val rightValueVar = LVar(1.0) + val rightValueInput: TypedTag[Input] = input(value := rightValueVar.now) + val (rightValueEvent: Event[String], renderedRightValue: Input) = RenderUtil.inputFieldHandler(rightValueInput, oninput, clear = false) - div(p("Unit Conversion with Lenses :D"), renderedCelsius, renderedKelvin) + div(p("Unit Conversion with Lenses"), renderedLeftUnit, renderedRightUnit, br, renderedLeftValue, renderedRightValue) } def testSignalLens() = { @@ -158,8 +161,8 @@ object ConversionTest { val rightInput: TypedTag[Input] = input(value := rightVar.now) val (rightEvent: Event[String], renderedRight: Input) = RenderUtil.inputFieldHandler(rightInput, oninput, clear = false) - leftVar.fire(leftEvent.map(str => str.toDouble)) - rightVar.fire(rightEvent.map(str => str.toDouble)) + leftVar.fire(leftEvent.map{toDoubleOr0(_)}) + rightVar.fire(rightEvent.map{toDoubleOr0(_)}) leftVar.observe{value =>; renderedLeft.value = value.toString} rightVar.observe{value =>; renderedRight.value = value.toString} @@ -195,4 +198,12 @@ object ConversionTest { Option[Double](meter.get * 0.9144) } + def toDoubleOr0(str : String): Double = { + try { + {str.toDouble} + } catch { + case _ => {0.0} + } + } + } diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index f9388a9fb..7d5803a8c 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -3,7 +3,6 @@ package rescala.operator import rescala.core.{AdmissionTicket, Base, CreationTicket, InitialChange, Observation, ReInfo, ReSource, Scheduler, ScopeSearch} import rescala.structure.Pulse -//import scala.language.implicitConversions import scala.util.Random trait SourceBundle { @@ -212,7 +211,7 @@ trait SourceBundle { } /** - * TODO: The BijectiveSigLens requires a reactive read without evaluating dependencies. As this is currently not supported by REScala, it uses _now_ instead! + * TODO: The BijectiveSigLens requires a reactive read without evaluating dependencies. As this is currently not supported by REScala, it uses .now instead! * @param lensSig * @tparam M * @tparam V @@ -229,11 +228,27 @@ trait SourceBundle { implicit def toSignalLens[M, V](lens: BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]): BijectiveSigLens[M, V] = BijectiveSigLens(Signal {lens}) + /** + * A simple lens for addition + * @param k The summand + */ class AddLens[A](k: A)(implicit num: Numeric[A]) extends BijectiveLens[A, A] { def toView(m: A): A = num.plus(m, k) def toModel(v: A): A = num.minus(v, k) } + /** + * A simple lens for multiplication + * @param k The summand + */ + class MulLens[A](k: A)(implicit frac: Fractional[A]) extends BijectiveLens[A, A] { + def toView(m: A): A = frac.times(m, k) + def toModel(v: A): A = frac.div(v, k) + } + + /** + * A simple lens with returns the identity + */ class NeutralLens[A] extends BijectiveLens[A, A] { def toView(m: A): A = m def toModel(v: A): A = v From c45b04820204d5ab86119411d0cf306b82ab04e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Mon, 29 Jan 2024 21:55:17 +0100 Subject: [PATCH 26/33] Finished TemperatureConverter --- .../src/main/scala/copl/ConversionTest.scala | 14 ++++++++------ .../src/main/scala/copl/RenderUtil.scala | 4 ++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 11a52b88a..2778cb6a1 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -27,10 +27,12 @@ object ConversionTest { () } + enum TempConversion(val lens: BijectiveLens[Double, Double]): case C extends TempConversion(new NeutralLens) case K extends TempConversion(new AddLens(274.15)) case L extends TempConversion(new AddLens(253)) + case F extends TempConversion(new MulLens(1.8).compose(new AddLens(32))) end TempConversion def conversionLens(from : TempConversion, to : TempConversion): BijectiveLens[Double, Double] = from.lens.inverse.compose(to.lens) @@ -110,8 +112,8 @@ object ConversionTest { rightVar.fire(rightEvent.map{toDoubleOr0(_)}) //TODO This is ugly - leftVar.observe { value => ; renderedLeft.value = value.toString } - rightVar.observe { value => ; renderedRight.value = value.toString } + leftVar.observe { value => RenderUtil.setInputDisplay(renderedLeft, value.toString) } + rightVar.observe { value => RenderUtil.setInputDisplay(renderedLeft, value.toString) } div(p("A simple Calculator"), renderedLeft, renderedOperation, renderedValue, " = ", renderedRight) } @@ -147,11 +149,11 @@ object ConversionTest { val rightValueInput: TypedTag[Input] = input(value := rightVar.now) val (rightValueEvent: Event[String], renderedRightValue: Input) = RenderUtil.inputFieldHandler(rightValueInput, oninput, clear = false) - leftVar.fire(leftValueEvent.map {str => if (str.toDoubleOption.isDefined) str.toDouble else 0.0 }) - rightVar.fire(rightValueEvent.map {str => if (str.toDoubleOption.isDefined) str.toDouble else 0.0 }) + leftVar.fire(leftValueEvent.map{toDoubleOr0(_)}) + rightVar.fire(rightValueEvent.map{toDoubleOr0(_)}) - rightVar.observe{value => ;renderedRightValue.value = value.toString} - leftVar.observe{value => ;renderedLeftValue.value = value.toString} + leftVar.observe{ value => RenderUtil.setInputDisplay(renderedLeftValue, value.toString) } + rightVar.observe{ value => RenderUtil.setInputDisplay(renderedRightValue, value.toString) } div(p("Unit Conversion with Lenses"), renderedLeftUnit, renderedRightUnit, br , renderedLeftValue, renderedRightValue) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala index 83bbb1d6d..3c8b5dd70 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/RenderUtil.scala @@ -38,4 +38,8 @@ object RenderUtil { (inputFieldText, todoInputField) } + + def setInputDisplay(in : Input, text : String): Unit = { + in.value = text + } } From 85abb5c83495129fd6df4603ce09292537e5c105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Mon, 12 Feb 2024 18:04:33 +0100 Subject: [PATCH 27/33] Implemented normal Lenses (Again) --- .../src/main/scala/copl/ConversionTest.scala | 157 +----------------- .../scala/rescala/operator/SourceBundle.scala | 96 ++++++----- 2 files changed, 56 insertions(+), 197 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 2778cb6a1..9df6a2bf7 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -1,20 +1,15 @@ package copl -import org.scalajs.dom.html.{Input, Paragraph, Select} +import org.scalajs.dom.html.{Input, Select} import org.scalajs.dom.document -import rescala.extra.Tags import rescala.interfaces.toposort import rescala.interfaces.toposort.* import scalatags.JsDom import scalatags.JsDom.all.* import scalatags.JsDom.TypedTag -import scala.annotation.switch import scala.scalajs.js.annotation.JSExportTopLevel -object TopoTags extends Tags[rescala.interfaces.toposort.type](rescala.interfaces.toposort, true) -import TopoTags.* - object ConversionTest { @JSExportTopLevel("UnitConversion") @@ -32,104 +27,11 @@ object ConversionTest { case C extends TempConversion(new NeutralLens) case K extends TempConversion(new AddLens(274.15)) case L extends TempConversion(new AddLens(253)) - case F extends TempConversion(new MulLens(1.8).compose(new AddLens(32))) + case F extends TempConversion(new MulLens(1.8).compose(new AddLens(32.0))) end TempConversion def conversionLens(from : TempConversion, to : TempConversion): BijectiveLens[Double, Double] = from.lens.inverse.compose(to.lens) - def signalTest() = { - - val evA: toposort.Evt[Int] = Evt[Int]() - val evB: toposort.Evt[Int] = Evt[Int]() - val evC: toposort.Evt[Int] = Evt[Int]() - val a = LVar(2) - val b = a.applyLens(new AddLens(10)) - val c = a.applyLens(new AddLens(-1)) - a.fire(evA) - b.fire(evB) - c.fire(evC) - a.getEvent().observe{value => println("Value of a changed to " + value)} - b.getEvent().observe{value => println("Value of b changed to " + value)} - c.getEvent().observe{value => println("Value of c changed to " + value)} - - println("Init Vars") - println(a.now) - println(b.now) - println(c.now) - - println("Changing a") - evA.fire(0) - println(a.now) - println(b.now) - println(c.now) - - println("Changing b") - evB.fire(0) - println(a.now) - println(b.now) - println(c.now) - - println("Changing c") - evC.fire(5) - println(a.now) - println(b.now) - println(c.now) - - } - - def basicCalculator() = { - val leftVar = LVar(0.0) - - //Handle operation dropdown - val operationInput: TypedTag[Select] = select(option("+"), option("-"), option("*"), option("/")) - val (operationEvent: Event[String], renderedOperation: Select) = RenderUtil.dropDownHandler(operationInput, oninput, clear = false) - val operationSignal: Signal[String] = operationEvent.hold(init = renderedOperation.value) - - //Handle value input - val valueInput: TypedTag[Input] = input(value := 0.0) - val (valueEvent: Event[String], renderedValue: Input) = RenderUtil.inputFieldHandler(valueInput, oninput, clear = false) - val valueSignal: Signal[Double] = valueEvent.hold(init = renderedValue.value).map{toDoubleOr0(_)} - - val lens : BijectiveSigLens[Double, Double] = new BijectiveSigLens[Double, Double](Signal{ - (operationSignal.value: @switch) match { - case "+" => new AddLens(valueSignal.value) - case "-" => new AddLens(valueSignal.value).inverse - case "*" => new MulLens(valueSignal.value) - case "/" => new MulLens(valueSignal.value).inverse - case _ => new NeutralLens - } - }) - - val rightVar = leftVar.applyLens(lens) - - val leftInput: TypedTag[Input] = input(value := leftVar.now) - val (leftEvent: Event[String], renderedLeft: Input) = RenderUtil.inputFieldHandler(leftInput, oninput, clear = false) - - val rightInput: TypedTag[Input] = input(value := rightVar.now) - val (rightEvent: Event[String], renderedRight: Input) = RenderUtil.inputFieldHandler(rightInput, oninput, clear = false) - - leftVar.fire(leftEvent.map{toDoubleOr0(_)}) - rightVar.fire(rightEvent.map{toDoubleOr0(_)}) - - //TODO This is ugly - leftVar.observe { value => RenderUtil.setInputDisplay(renderedLeft, value.toString) } - rightVar.observe { value => RenderUtil.setInputDisplay(renderedLeft, value.toString) } - - div(p("A simple Calculator"), renderedLeft, renderedOperation, renderedValue, " = ", renderedRight) - } - - def getOneWayConverter() = { - - val meterInput: TypedTag[Input] = input(placeholder := "Meters") - val (meterEvent: Event[String], renderedMeter: Input) = RenderUtil.inputFieldHandler(meterInput, oninput, clear = false) - - val yardSignal: Signal[Option[Double]] = meterEvent.hold(init="Please enter a valid value for meters.").map { str => convertMeterToYard(str.toDoubleOption)} - - val yardParagraph: Signal[TypedTag[Paragraph]] = yardSignal.map { yard => p(if (yard.isEmpty) "Please enter a valid value for meters." else yard.get.toString)} - - div(p("One way conversion using Signal"), renderedMeter, yardParagraph.asModifier) - } - def unitConverter() = { val leftUnitInput: TypedTag[Select] = select(TempConversion.values.map{ unit => option(unit.toString) }) @@ -141,7 +43,7 @@ object ConversionTest { val rightUnitSignal: Signal[TempConversion] = rightUnitEvent.hold(init = renderedRightUnit.value).map{TempConversion.valueOf(_)} val leftVar = LVar(0.0) - val rightVar = leftVar.applyLens(BijectiveSigLens(Signal{conversionLens(leftUnitSignal.value, rightUnitSignal.value)})) + val rightVar = leftVar.applyLens(LensSig(Signal{conversionLens(leftUnitSignal.value, rightUnitSignal.value)})) val leftValueInput: TypedTag[Input] = input(value := leftVar.now) val (leftValueEvent: Event[String], renderedLeftValue: Input) = RenderUtil.inputFieldHandler(leftValueInput, oninput, clear = false) @@ -159,59 +61,6 @@ object ConversionTest { } - def testSignalLens() = { - val leftVar = LVar(0.0) - - val sigInput: TypedTag[Input] = input(value := 3.0) - val (sigEvent: Event[String], renderedSig: Input) = RenderUtil.inputFieldHandler(sigInput, oninput, clear = false) - - val lensSig = Signal{ new AddLens(sigEvent.hold(init = renderedSig.value).map {(str : String) => (str.toDouble : Double)}.value).inverse} - - val rightVar = leftVar.applyLens(new BijectiveSigLens(lensSig)) - - val leftInput: TypedTag[Input] = input(value := leftVar.now) - val (leftEvent: Event[String], renderedLeft: Input) = RenderUtil.inputFieldHandler(leftInput, oninput, clear = false) - - val rightInput: TypedTag[Input] = input(value := rightVar.now) - val (rightEvent: Event[String], renderedRight: Input) = RenderUtil.inputFieldHandler(rightInput, oninput, clear = false) - - leftVar.fire(leftEvent.map{toDoubleOr0(_)}) - rightVar.fire(rightEvent.map{toDoubleOr0(_)}) - - leftVar.observe{value =>; renderedLeft.value = value.toString} - rightVar.observe{value =>; renderedRight.value = value.toString} - - div(p("Unit Conversion with Lenses :D"), renderedLeft, renderedSig, renderedRight) - } - - - def toStringConverter() = { - - val intVar = LVar(0) - val strVar = intVar.applyLens(new NonDeterministicCharCountLens()) - - val intInput: TypedTag[Input] = input(value := intVar.now) - val (intEvent: Event[String], renderedInt: Input) = RenderUtil.inputFieldHandler(intInput, oninput, clear = false) - - val strInput: TypedTag[Input] = input(value := strVar.now) - val (strEvent: Event[String], renderedStr: Input) = RenderUtil.inputFieldHandler(strInput, oninput, clear = false) - - intVar.fire(intEvent.map{_.toInt}) - strVar.fire(strEvent) - - intVar.observe{value => ; renderedInt.value = value.toString} - strVar.observe{value => ; renderedStr.value = value} - - div(p("Unit Conversion with Lenses :D"), renderedInt, renderedStr) - } - - def convertMeterToYard(meter : Option[Double]): Option[Double] = { - if(meter.isEmpty) - Option.empty[Double] - else - Option[Double](meter.get * 0.9144) - } - def toDoubleOr0(str : String): Double = { try { {str.toDouble} diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index 7d5803a8c..bd282e052 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -3,8 +3,6 @@ package rescala.operator import rescala.core.{AdmissionTicket, Base, CreationTicket, InitialChange, Observation, ReInfo, ReSource, Scheduler, ScopeSearch} import rescala.structure.Pulse -import scala.util.Random - trait SourceBundle { self: Operators => @@ -109,6 +107,12 @@ trait SourceBundle { } } + /** + * TODO Check chained compose & inverse wrt other lens functions + * TODO Inversion of LensSig + * TODO Other example + */ + /** * LVars serve as the basis for reactive lenses. TODO: Link Documentation * @param state The state of the LVar @@ -117,13 +121,14 @@ trait SourceBundle { class LVar[M] private[rescala](state : Signal[M], events : Evt[Event[M]]) { /** + * TODO: The BijectiveSigLens requires a reactive read without evaluating dependencies. As this is currently not supported by REScala, it uses .now instead! * Creates a new LVar which is connected to this LVar via the given Lens. * @param lens The lens which connects the LVars. Can use implicit conversion from BijectiveLens if the Lens does not need to change later */ - def applyLens[V](lens: BijectiveSigLens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]): LVar[V] = { + def applyLens[V](lens: LensSig[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]): LVar[V] = { val newVar = new LVar[V](state.map { model => lens.toView(model) }.flatten, Evt()) - events.fire(newVar.getEvent().map { e => lens.toModel(e) }) + events.fire(newVar.getEvent().map { e => lens.toModel(e, this.state.now) }) return newVar } @@ -145,7 +150,7 @@ trait SourceBundle { /** * Function to access the current value of the lens. Simple wrapper for internal.now */ - def now(implicit sched: Scheduler[BundleState]) : M = state.now + inline def now(implicit sched: Scheduler[BundleState]) : M = state.now /** * Function to access state of LVar in reactives. Simple wrapper for internal.value. @@ -167,10 +172,35 @@ trait SourceBundle { } - //TODO: Note that non-bijective lenses are not supported due to the chosen propagation. Only keep for demonstration + /** + * The base type for all lenses. If possible, use BijectiveLens instead as it provides more performance and additional functionality + * @tparam M + * @tparam V + */ trait Lens[M, V] { + + /** + * Transforms the model to the view + */ def toView(m: M): V + + /** + * Transforms the view to the model using the old model state + */ def toModel(v: V, m: M): M + + /** + * Concatenates this lens with another lens and returns the resulting lens. + * Internally, a LensVar is created, meaning that this function is just for convenience and not for performance + * @param other The other lens + */ + def compose[W](other: Lens[V, W]): Lens[M, W] = new Lens[M, W] { + override def toView(m: M): W = other.toView(Lens.this.toView(m)) + override def toModel(w: W, m : M): M = { + val viewOfA = Lens.this.toView(m) + Lens.this.toModel(other.toModel(w, viewOfA), m) + } + } } /** @@ -178,47 +208,41 @@ trait SourceBundle { * @tparam M The type of the model * @tparam V The type of the view */ - trait BijectiveLens[M, V] { - /** - * Transforms the model to the view - */ - def toView(m: M): V + trait BijectiveLens[M, V] extends Lens[M, V]{ /** - * Transforms the view to the model + * Override the toModel function to make the lens bijective */ def toModel(v: V): M - - //TODO: Better way to pass this than via implicit? + override def toModel(v: V, m: M) = toModel(v) /** * Inverts the lens such that e.g. an AddLens functions like a SubLens. Note that this does not change the model-view relationship, * i.e. the asymmetry is not inverted. */ - def inverse(implicit lens: BijectiveLens[M, V] = this): BijectiveLens[V, M] = new BijectiveLens[V, M] { - override def toView(m: V): M = lens.toModel(m) - override def toModel(v: M): V = lens.toView(v) + def inverse: BijectiveLens[V, M] = new BijectiveLens[V, M] { + override def toView(m: V): M = BijectiveLens.this.toModel(m) + override def toModel(v: M): V = BijectiveLens.this.toView(v) } /** - * Concatenates this lens with another lens and returns the resulting lens. + * Overloads the compose function to return a bijective lens + * This version does not use an LVar internally, making it more efficient * @param other The other lens */ - def compose[W](other: BijectiveLens[V, W])(implicit lens: BijectiveLens[M, V] = this): BijectiveLens[M, W] = new BijectiveLens[M, W] { - override def toView(m: M) : W = other.toView(lens.toView(m)) - override def toModel(w: W): M = lens.toModel(other.toModel(w)) + def compose[W](other: BijectiveLens[V, W]): BijectiveLens[M, W] = new BijectiveLens[M, W] { + override def toView(m: M) : W = other.toView(BijectiveLens.this.toView(m)) + override def toModel(w: W): M = BijectiveLens.this.toModel(other.toModel(w)) } } /** - * TODO: The BijectiveSigLens requires a reactive read without evaluating dependencies. As this is currently not supported by REScala, it uses .now instead! - * @param lensSig - * @tparam M - * @tparam V + * TODO: The LensSig requires a reactive read without evaluating dependencies. As this is currently not supported by REScala, it uses .now instead! + * @param lensSig A Signal of a Lens */ - class BijectiveSigLens[M, V](lensSig : Signal[BijectiveLens[M, V]])(implicit sched: Scheduler[BundleState]){ - def toView(m: M) : Signal[V] = lensSig.map { model => model.toView(m) } - def toModel(v: V) : M = lensSig.now.toModel(v) + class LensSig[M, V](lensSig: Signal[Lens[M, V]])(implicit sched: Scheduler[BundleState]) { + def toView(m: M): Signal[V] = lensSig.map { model => model.toView(m) } + def toModel(v: V, m :M): M = lensSig.now.toModel(v, m) } /** @@ -226,7 +250,7 @@ trait SourceBundle { * Could instead also be handled by method overloading (currently only LVar.applyLens(..)) for slightly more efficiency. */ implicit def toSignalLens[M, V](lens: BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState], - sched: Scheduler[BundleState]): BijectiveSigLens[M, V] = BijectiveSigLens(Signal {lens}) + sched: Scheduler[BundleState]): LensSig[M, V] = LensSig(Signal {lens}) /** * A simple lens for addition @@ -253,18 +277,4 @@ trait SourceBundle { def toView(m: A): A = m def toModel(v: A): A = v } - - //IS NOT BIJECTIVE - class NonDeterministicCharCountLens extends BijectiveLens[Int, String] { - // Lens that converts an int to a sequence of chars, eg. 5 => "aaaaa" - def toView(m: Int): String = Random.alphanumeric.take(m).mkString - def toModel(v: String): Int = v.length - } - - class UpperCharLens extends BijectiveLens[String, String]{ - //shift up a char's letters to all caps - //würde aber eh noch so "if-checks" benötigen, dass man vorher überprüft obs schon upperCase ist or not - def toView(m: String): String = m.toUpperCase() - def toModel(v: String): String = v.toLowerCase() - } } From c6332559d09847671673e7e220a8f9024cbbcd92 Mon Sep 17 00:00:00 2001 From: Josef552 <105931716+Josef552@users.noreply.github.com> Date: Wed, 14 Feb 2024 13:04:03 +0100 Subject: [PATCH 28/33] Update SourceBundle.scala --- .../shared/src/main/scala/rescala/operator/SourceBundle.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index bd282e052..4052d011c 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -242,14 +242,14 @@ trait SourceBundle { */ class LensSig[M, V](lensSig: Signal[Lens[M, V]])(implicit sched: Scheduler[BundleState]) { def toView(m: M): Signal[V] = lensSig.map { model => model.toView(m) } - def toModel(v: V, m :M): M = lensSig.now.toModel(v, m) + def toModel(v: V, m : M): M = lensSig.now.toModel(v, m) } /** * Implicit conversion of a BijectiveLens to a BijectiveSigLens for uniform handling. * Could instead also be handled by method overloading (currently only LVar.applyLens(..)) for slightly more efficiency. */ - implicit def toSignalLens[M, V](lens: BijectiveLens[M, V])(implicit ticket: CreationTicket[BundleState], + implicit def toSignalLens[M, V](lens: Lens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]): LensSig[M, V] = LensSig(Signal {lens}) /** From bf6e883d09a7193d8cfda4ffb231cc28ed1d1638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Wed, 28 Feb 2024 14:52:18 +0100 Subject: [PATCH 29/33] Documentation --- .../src/main/scala/copl/ConversionTest.scala | 48 +++++++++++++++++-- .../scala/rescala/operator/SourceBundle.scala | 43 ++++++++--------- 2 files changed, 65 insertions(+), 26 deletions(-) diff --git a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala index 9df6a2bf7..8cb7e79f5 100644 --- a/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala +++ b/Modules/Example ReactiveLenses/src/main/scala/copl/ConversionTest.scala @@ -16,24 +16,36 @@ object ConversionTest { def run(): Unit = main(Array.empty[String]) def main(args: Array[String]): Unit = { -// signalTest() val temperatureConverter = unitConverter() document.body.replaceChild(temperatureConverter.render, document.body.firstChild) () } + /** + * Contains all supported units with their conversion from Celsius. To add support for a unit, simply add it to the + * enum with the lens representing the corresponding conversion + * @param lens the conversion lens from Celsius + */ enum TempConversion(val lens: BijectiveLens[Double, Double]): + //All Conversions are given from Celsius case C extends TempConversion(new NeutralLens) case K extends TempConversion(new AddLens(274.15)) case L extends TempConversion(new AddLens(253)) case F extends TempConversion(new MulLens(1.8).compose(new AddLens(32.0))) end TempConversion + /** + * Creates a lens which converts between any supported units + */ def conversionLens(from : TempConversion, to : TempConversion): BijectiveLens[Double, Double] = from.lens.inverse.compose(to.lens) + /** + * A demonstration of reactive lenses using a simple unit converter for temperature units + */ def unitConverter() = { + //Create selection for units and convert selected units to signals val leftUnitInput: TypedTag[Select] = select(TempConversion.values.map{ unit => option(unit.toString) }) val (leftUnitEvent: Event[String], renderedLeftUnit: Select) = RenderUtil.dropDownHandler(leftUnitInput, oninput, clear = false) val leftUnitSignal: Signal[TempConversion] = leftUnitEvent.hold(init = renderedLeftUnit.value).map{TempConversion.valueOf(_)} @@ -42,25 +54,32 @@ object ConversionTest { val (rightUnitEvent: Event[String], renderedRightUnit: Select) = RenderUtil.dropDownHandler(rightUnitInput, oninput, clear = false) val rightUnitSignal: Signal[TempConversion] = rightUnitEvent.hold(init = renderedRightUnit.value).map{TempConversion.valueOf(_)} + //Create the two LVars containing the left and right value using reactive lenses. val leftVar = LVar(0.0) - val rightVar = leftVar.applyLens(LensSig(Signal{conversionLens(leftUnitSignal.value, rightUnitSignal.value)})) + val rightVar = leftVar.applyLens(SignalLens(Signal{conversionLens(leftUnitSignal.value, rightUnitSignal.value)})) + //Create text fields and input events for the values val leftValueInput: TypedTag[Input] = input(value := leftVar.now) val (leftValueEvent: Event[String], renderedLeftValue: Input) = RenderUtil.inputFieldHandler(leftValueInput, oninput, clear = false) val rightValueInput: TypedTag[Input] = input(value := rightVar.now) val (rightValueEvent: Event[String], renderedRightValue: Input) = RenderUtil.inputFieldHandler(rightValueInput, oninput, clear = false) + //Register input events as source of change for LVars leftVar.fire(leftValueEvent.map{toDoubleOr0(_)}) rightVar.fire(rightValueEvent.map{toDoubleOr0(_)}) + //Observe LVars to update UI leftVar.observe{ value => RenderUtil.setInputDisplay(renderedLeftValue, value.toString) } rightVar.observe{ value => RenderUtil.setInputDisplay(renderedRightValue, value.toString) } + //Combine all UI elements div(p("Unit Conversion with Lenses"), renderedLeftUnit, renderedRightUnit, br , renderedLeftValue, renderedRightValue) - } + /** + * Returns the double represented by the string or 0 if no double is represented + */ def toDoubleOr0(str : String): Double = { try { {str.toDouble} @@ -69,4 +88,27 @@ object ConversionTest { } } + /** + * A demonstration of the effect of declaration order on event execution if an event effects multiple LVars in the same + * cluster. When inverting the definition of b and c, the output changes. + */ + def raceConditionTest(): Unit = { + val a = LVar(0) + val b = a.applyLens(new AddLens(1)) + val c = a.applyLens(new AddLens(2)) + + println("a is " + a.now.toString) + println("b is " + b.now.toString) + println("c is " + c.now.toString) + + val e = Evt[Int]() + b.fire(e) + c.fire(e) + e.fire(3) + println("\n Fire event...") + println("a is " + a.now.toString) + println("b is " + b.now.toString) + println("c is " + c.now.toString) + } + } diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index 4052d011c..07efbb411 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -108,25 +108,21 @@ trait SourceBundle { } /** - * TODO Check chained compose & inverse wrt other lens functions - * TODO Inversion of LensSig - * TODO Other example - */ - - /** - * LVars serve as the basis for reactive lenses. TODO: Link Documentation + * LVars serve as the basis for reactive lenses. To create the root of a new LVar cluster, use the apply() function + * of the LVar object. Then, connect additional LVars via Lenses using the applyLens() function of an existing LVar. + * * @param state The state of the LVar - * @param events TODO How to explain this? + * @param events Incoming events indicating a change to the LVar cluster */ class LVar[M] private[rescala](state : Signal[M], events : Evt[Event[M]]) { /** * TODO: The BijectiveSigLens requires a reactive read without evaluating dependencies. As this is currently not supported by REScala, it uses .now instead! * Creates a new LVar which is connected to this LVar via the given Lens. - * @param lens The lens which connects the LVars. Can use implicit conversion from BijectiveLens if the Lens does not need to change later + * @param lens The lens which connects the LVars. Can use implicit conversion from Lens if the Lens does not need to change later */ - def applyLens[V](lens: LensSig[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]): LVar[V] = { + def applyLens[V](lens: SignalLens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]): LVar[V] = { val newVar = new LVar[V](state.map { model => lens.toView(model) }.flatten, Evt()) events.fire(newVar.getEvent().map { e => lens.toModel(e, this.state.now) }) return newVar @@ -144,7 +140,9 @@ trait SourceBundle { def observe(onValue: M => Unit, onError: Throwable => Unit = null, fireImmediately: Boolean = false) (implicit ticket: CreationTicket[BundleState]) = state.observe(onValue, onError, fireImmediately) - //TODO Documentation + /** + * Returns the first firing event of all registered events + */ def getEvent()(implicit ticket: CreationTicket[BundleState]) : Event[M] = events.list().flatten(firstFiringEvent) /** @@ -174,8 +172,8 @@ trait SourceBundle { /** * The base type for all lenses. If possible, use BijectiveLens instead as it provides more performance and additional functionality - * @tparam M - * @tparam V + * @tparam M the type of the model + * @tparam V the type of the view */ trait Lens[M, V] { @@ -191,7 +189,7 @@ trait SourceBundle { /** * Concatenates this lens with another lens and returns the resulting lens. - * Internally, a LensVar is created, meaning that this function is just for convenience and not for performance + * Internally, a LensVar is created, meaning that this function is just for convenience and not performance * @param other The other lens */ def compose[W](other: Lens[V, W]): Lens[M, W] = new Lens[M, W] { @@ -227,7 +225,7 @@ trait SourceBundle { /** * Overloads the compose function to return a bijective lens - * This version does not use an LVar internally, making it more efficient + * This version does not use an LVar internally, making it more efficient than the implementation in Lens * @param other The other lens */ def compose[W](other: BijectiveLens[V, W]): BijectiveLens[M, W] = new BijectiveLens[M, W] { @@ -237,20 +235,19 @@ trait SourceBundle { } /** - * TODO: The LensSig requires a reactive read without evaluating dependencies. As this is currently not supported by REScala, it uses .now instead! - * @param lensSig A Signal of a Lens + * TODO: The SignalLens requires a reactive read without evaluating dependencies. As this is currently not supported by REScala, it uses .now instead! + * @param signalOfLens A Signal of a Lens */ - class LensSig[M, V](lensSig: Signal[Lens[M, V]])(implicit sched: Scheduler[BundleState]) { - def toView(m: M): Signal[V] = lensSig.map { model => model.toView(m) } - def toModel(v: V, m : M): M = lensSig.now.toModel(v, m) + class SignalLens[M, V](signalOfLens: Signal[Lens[M, V]])(implicit sched: Scheduler[BundleState]) { + def toView(m: M): Signal[V] = signalOfLens.map { model => model.toView(m) } + def toModel(v: V, m : M): M = signalOfLens.now.toModel(v, m) } /** - * Implicit conversion of a BijectiveLens to a BijectiveSigLens for uniform handling. - * Could instead also be handled by method overloading (currently only LVar.applyLens(..)) for slightly more efficiency. + * Implicit conversion of a Lens to a SignalLens for uniform handling. */ implicit def toSignalLens[M, V](lens: Lens[M, V])(implicit ticket: CreationTicket[BundleState], - sched: Scheduler[BundleState]): LensSig[M, V] = LensSig(Signal {lens}) + sched: Scheduler[BundleState]): SignalLens[M, V] = SignalLens(Signal {lens}) /** * A simple lens for addition From e8413d76adc20e8f29cf05db1d4f463cd33c1d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Thu, 7 Mar 2024 14:40:45 +0100 Subject: [PATCH 30/33] Moved Lenses to rescala.operator.LensBundle --- .../scala/rescala/operator/LensBundle.scala | 266 +++++++++++++----- .../scala/rescala/operator/Operators.scala | 3 +- .../scala/rescala/operator/SourceBundle.scala | 169 +---------- 3 files changed, 198 insertions(+), 240 deletions(-) diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala index bcf071bb1..f8aec752a 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala @@ -1,71 +1,195 @@ -//package rescala.operator -// -//trait LensBundle { -// //selfType: Operators => -// -// trait Lens[M, V] { -// def toView(m: M): V -// -// def toModel(v: V, m: M): M -// } -// -// trait BijectiveLens[M, V] extends Lens[M, V] { -// def toView(m: M): V -// -// def toModel(v: V): M -// -// def toModel(v: V, m: M): M = toModel(v) -// } -// -// class AddLens[A](k: A)(implicit num: Numeric[A]) extends BijectiveLens[A, A] { -// def toView(m: A): A = num.plus(m, k) -// -// def toModel(v: A): A = num.minus(v, k) -// } - - -// class LVar extends Var() -// -// //das war ja eher nur einen auf fancy shmancy machen, also fuer uns eher irrelevant (?) -// def LVar[A](init: A): LVar[A] -// def applyLens[B](lens: Lens[A, B]): LVar[B] -// -// class FractionalLVar[A: Fractional](lvar: LVar[A]) { -// def *(k:A) = lvar.applyLens(new MulLens(k)) -// } -//} -// -// //like this? -// class SubtractLens[A](k: A)(implicit num: Numeric[A]) extends BijectiveLens[A, A] { -// def toView(m: A): A = num.minus(m, k) -// def toModel(v: A): A = num.plus(v, k) -// } -// -// class MulLens[A](k: A)(implicit frac: Fractional[A]) extends Lens[A, A] { -// if(k==0) throw new IllegalArgumentException("Illegal Lens: mul/div by zero") -// -// def toView(m: A): A = frac.times(m, k) -// def toModel(v: A, m: A): A = { -// val res = frac.div(v, k) -// if(frac.equiv(res, m)) m -// else res -// } -// } -// -// //analog auch für andere arithmetische Operations-Lenses -// //Division, Exponential, Wurzel, Log (?) -// -// /** -// //das war ja eher nur einen auf fancy shmancy machen, also fuer uns eher irrelevant (?) -// def LVar[A](init: A): LVar[A] -// def applyLens[B](lens: Lens[A, B]): LVar[B] -// -// implicit def toFracLVar[A: Fractional](lvar: LVar[A]) = new FractionalLVar(lvar) -// -// class FractionalLVar[A: Fractional](lvar: LVar[A]) { -// def *(k:A) = lvar.applyLens(new MulLens(k)) -// } -// **/ -// -// -//} +package rescala.operator + +import rescala.core.* + +trait LensBundle { + self: Operators => + + /** + * LVars serve as the basis for reactive lenses. To create the root of a new LVar cluster, use the apply() function + * of the LVar object. Then, connect additional LVars via Lenses using the applyLens() function of an existing LVar. + * + * @param state The state of the LVar + * @param events Incoming events indicating a change to the LVar cluster + */ + class LVar[M] private[rescala](state: Signal[M], events: Evt[Event[M]]) { + + /** + * TODO: The BijectiveSigLens requires a reactive read without evaluating dependencies. As this is currently not supported by REScala, it uses .now instead! + * Creates a new LVar which is connected to this LVar via the given Lens. + * + * @param lens The lens which connects the LVars. Can use implicit conversion from Lens if the Lens does not need to change later + */ + + def applyLens[V](lens: SignalLens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]): LVar[V] = { + val newVar = new LVar[V](state.map { model => lens.toView(model) }.flatten, Evt()) + events.fire(newVar.getEvent().map { e => lens.toModel(e, this.state.now) }) + return newVar + } + + /** + * Register an event which should trigger a change of this LVar (and consequently the entire lens cluster) + * + * @param e The event which should change the LVar + */ + def fire(e: Event[M])(implicit sched: Scheduler[BundleState]): Unit = events.fire(e) + + /** + * Function to observe a change of the LVar. Simple wrapper for internal.observe + */ + def observe(onValue: M => Unit, onError: Throwable => Unit = null, fireImmediately: Boolean = false) + (implicit ticket: CreationTicket[BundleState]) = state.observe(onValue, onError, fireImmediately) + + /** + * Returns the first firing event of all registered events + */ + def getEvent()(implicit ticket: CreationTicket[BundleState]): Event[M] = events.list().flatten(firstFiringEvent) + + /** + * Function to access the current value of the lens. Simple wrapper for internal.now + */ + inline def now(implicit sched: Scheduler[BundleState]): M = state.now + + /** + * Function to access state of LVar in reactives. Simple wrapper for internal.value. + */ + inline def value(implicit sched: Scheduler[BundleState]): M = state.value + + } + + object LVar { + + /** + * Creates a new LVar with the given initial value. This will be the root of a new Lens cluster + * + * @param initval the inital value of the LVar + */ + def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = { + val events: Evt[Event[T]] = Evt() + new LVar[T](events.list().flatten(firstFiringEvent).hold(initval), events) + } + + } + + /** + * The base type for all lenses. If possible, use BijectiveLens instead as it provides more performance and additional functionality + * + * @tparam M the type of the model + * @tparam V the type of the view + */ + trait Lens[M, V] { + + /** + * Transforms the model to the view + */ + def toView(m: M): V + + /** + * Transforms the view to the model using the old model state + */ + def toModel(v: V, m: M): M + + /** + * Concatenates this lens with another lens and returns the resulting lens. + * Internally, a LensVar is created, meaning that this function is just for convenience and not performance + * + * @param other The other lens + */ + def compose[W](other: Lens[V, W]): Lens[M, W] = new Lens[M, W] { + override def toView(m: M): W = other.toView(Lens.this.toView(m)) + + override def toModel(w: W, m: M): M = { + val viewOfA = Lens.this.toView(m) + Lens.this.toModel(other.toModel(w, viewOfA), m) + } + } + } + + /** + * The base trait for all bijective lenses + * + * @tparam M The type of the model + * @tparam V The type of the view + */ + trait BijectiveLens[M, V] extends Lens[M, V] { + + /** + * Override the toModel function to make the lens bijective + */ + def toModel(v: V): M + + override def toModel(v: V, m: M) = toModel(v) + + /** + * Inverts the lens such that e.g. an AddLens functions like a SubLens. Note that this does not change the model-view relationship, + * i.e. the asymmetry is not inverted. + */ + def inverse: BijectiveLens[V, M] = new BijectiveLens[V, M] { + override def toView(m: V): M = BijectiveLens.this.toModel(m) + + override def toModel(v: M): V = BijectiveLens.this.toView(v) + } + + /** + * Overloads the compose function to return a bijective lens + * This version does not use an LVar internally, making it more efficient than the implementation in Lens + * + * @param other The other lens + */ + def compose[W](other: BijectiveLens[V, W]): BijectiveLens[M, W] = new BijectiveLens[M, W] { + override def toView(m: M): W = other.toView(BijectiveLens.this.toView(m)) + + override def toModel(w: W): M = BijectiveLens.this.toModel(other.toModel(w)) + } + } + + /** + * TODO: The SignalLens requires a reactive read without evaluating dependencies. As this is currently not supported by REScala, it uses .now instead! + * + * @param signalOfLens A Signal of a Lens + */ + class SignalLens[M, V](signalOfLens: Signal[Lens[M, V]])(implicit sched: Scheduler[BundleState]) { + def toView(m: M): Signal[V] = signalOfLens.map { model => model.toView(m) } + + def toModel(v: V, m: M): M = signalOfLens.now.toModel(v, m) + } + + /** + * Implicit conversion of a Lens to a SignalLens for uniform handling. + */ + implicit def toSignalLens[M, V](lens: Lens[M, V])(implicit ticket: CreationTicket[BundleState], + sched: Scheduler[BundleState]): SignalLens[M, V] = SignalLens(Signal { + lens + }) + + /** + * A simple lens for addition + * + * @param k The summand + */ + class AddLens[A](k: A)(implicit num: Numeric[A]) extends BijectiveLens[A, A] { + def toView(m: A): A = num.plus(m, k) + + def toModel(v: A): A = num.minus(v, k) + } + + /** + * A simple lens for multiplication + * + * @param k The summand + */ + class MulLens[A](k: A)(implicit frac: Fractional[A]) extends BijectiveLens[A, A] { + def toView(m: A): A = frac.times(m, k) + + def toModel(v: A): A = frac.div(v, k) + } + + /** + * A simple lens with returns the identity + */ + class NeutralLens[A] extends BijectiveLens[A, A] { + def toView(m: A): A = m + + def toModel(v: A): A = v + } +} diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/Operators.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/Operators.scala index 5d5a9d666..c448c8225 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/Operators.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/Operators.scala @@ -11,6 +11,7 @@ trait Operators extends AnyRef // to make the below more symmetric with SignalBundle with FoldBundle with FlattenBundle - with SourceBundle { + with SourceBundle + with LensBundle { type BundleState[_] } diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala index 07efbb411..c251df22a 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/SourceBundle.scala @@ -106,172 +106,5 @@ trait SourceBundle { ticket.createSource[Pulse[T], Var[T]](change)(s => new Var[T](s, ticket.info)) } } - - /** - * LVars serve as the basis for reactive lenses. To create the root of a new LVar cluster, use the apply() function - * of the LVar object. Then, connect additional LVars via Lenses using the applyLens() function of an existing LVar. - * - * @param state The state of the LVar - * @param events Incoming events indicating a change to the LVar cluster - */ - class LVar[M] private[rescala](state : Signal[M], events : Evt[Event[M]]) { - - /** - * TODO: The BijectiveSigLens requires a reactive read without evaluating dependencies. As this is currently not supported by REScala, it uses .now instead! - * Creates a new LVar which is connected to this LVar via the given Lens. - * @param lens The lens which connects the LVars. Can use implicit conversion from Lens if the Lens does not need to change later - */ - - def applyLens[V](lens: SignalLens[M, V])(implicit ticket: CreationTicket[BundleState], sched: Scheduler[BundleState]): LVar[V] = { - val newVar = new LVar[V](state.map { model => lens.toView(model) }.flatten, Evt()) - events.fire(newVar.getEvent().map { e => lens.toModel(e, this.state.now) }) - return newVar - } - - /** - * Register an event which should trigger a change of this LVar (and consequently the entire lens cluster) - * @param e The event which should change the LVar - */ - def fire(e: Event[M])(implicit sched: Scheduler[BundleState]) : Unit = events.fire(e) - - /** - * Function to observe a change of the LVar. Simple wrapper for internal.observe - */ - def observe(onValue: M => Unit, onError: Throwable => Unit = null, fireImmediately: Boolean = false) - (implicit ticket: CreationTicket[BundleState]) = state.observe(onValue, onError, fireImmediately) - - /** - * Returns the first firing event of all registered events - */ - def getEvent()(implicit ticket: CreationTicket[BundleState]) : Event[M] = events.list().flatten(firstFiringEvent) - - /** - * Function to access the current value of the lens. Simple wrapper for internal.now - */ - inline def now(implicit sched: Scheduler[BundleState]) : M = state.now - - /** - * Function to access state of LVar in reactives. Simple wrapper for internal.value. - */ - inline def value(implicit sched: Scheduler[BundleState]) : M = state.value - - } - - object LVar { - - /** - * Creates a new LVar with the given initial value. This will be the root of a new Lens cluster - * @param initval the inital value of the LVar - */ - def apply[T](initval: T)(implicit ticket: CreationTicket[BundleState]): LVar[T] = { - val events : Evt[Event[T]] = Evt() - new LVar[T](events.list().flatten(firstFiringEvent).hold(initval), events) - } - - } - - /** - * The base type for all lenses. If possible, use BijectiveLens instead as it provides more performance and additional functionality - * @tparam M the type of the model - * @tparam V the type of the view - */ - trait Lens[M, V] { - - /** - * Transforms the model to the view - */ - def toView(m: M): V - - /** - * Transforms the view to the model using the old model state - */ - def toModel(v: V, m: M): M - - /** - * Concatenates this lens with another lens and returns the resulting lens. - * Internally, a LensVar is created, meaning that this function is just for convenience and not performance - * @param other The other lens - */ - def compose[W](other: Lens[V, W]): Lens[M, W] = new Lens[M, W] { - override def toView(m: M): W = other.toView(Lens.this.toView(m)) - override def toModel(w: W, m : M): M = { - val viewOfA = Lens.this.toView(m) - Lens.this.toModel(other.toModel(w, viewOfA), m) - } - } - } - - /** - * The base trait for all bijective lenses - * @tparam M The type of the model - * @tparam V The type of the view - */ - trait BijectiveLens[M, V] extends Lens[M, V]{ - - /** - * Override the toModel function to make the lens bijective - */ - def toModel(v: V): M - override def toModel(v: V, m: M) = toModel(v) - - /** - * Inverts the lens such that e.g. an AddLens functions like a SubLens. Note that this does not change the model-view relationship, - * i.e. the asymmetry is not inverted. - */ - def inverse: BijectiveLens[V, M] = new BijectiveLens[V, M] { - override def toView(m: V): M = BijectiveLens.this.toModel(m) - override def toModel(v: M): V = BijectiveLens.this.toView(v) - } - - /** - * Overloads the compose function to return a bijective lens - * This version does not use an LVar internally, making it more efficient than the implementation in Lens - * @param other The other lens - */ - def compose[W](other: BijectiveLens[V, W]): BijectiveLens[M, W] = new BijectiveLens[M, W] { - override def toView(m: M) : W = other.toView(BijectiveLens.this.toView(m)) - override def toModel(w: W): M = BijectiveLens.this.toModel(other.toModel(w)) - } - } - - /** - * TODO: The SignalLens requires a reactive read without evaluating dependencies. As this is currently not supported by REScala, it uses .now instead! - * @param signalOfLens A Signal of a Lens - */ - class SignalLens[M, V](signalOfLens: Signal[Lens[M, V]])(implicit sched: Scheduler[BundleState]) { - def toView(m: M): Signal[V] = signalOfLens.map { model => model.toView(m) } - def toModel(v: V, m : M): M = signalOfLens.now.toModel(v, m) - } - - /** - * Implicit conversion of a Lens to a SignalLens for uniform handling. - */ - implicit def toSignalLens[M, V](lens: Lens[M, V])(implicit ticket: CreationTicket[BundleState], - sched: Scheduler[BundleState]): SignalLens[M, V] = SignalLens(Signal {lens}) - - /** - * A simple lens for addition - * @param k The summand - */ - class AddLens[A](k: A)(implicit num: Numeric[A]) extends BijectiveLens[A, A] { - def toView(m: A): A = num.plus(m, k) - def toModel(v: A): A = num.minus(v, k) - } - - /** - * A simple lens for multiplication - * @param k The summand - */ - class MulLens[A](k: A)(implicit frac: Fractional[A]) extends BijectiveLens[A, A] { - def toView(m: A): A = frac.times(m, k) - def toModel(v: A): A = frac.div(v, k) - } - - /** - * A simple lens with returns the identity - */ - class NeutralLens[A] extends BijectiveLens[A, A] { - def toView(m: A): A = m - def toModel(v: A): A = v - } + } From 0358c637a5b208849d8a4be4af060b6e647324b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20R=C3=BCbberdt?= Date: Thu, 7 Mar 2024 14:56:16 +0100 Subject: [PATCH 31/33] Fixed Merge issues --- build.sbt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 3fd15f9d8..42d3275ad 100644 --- a/build.sbt +++ b/build.sbt @@ -159,7 +159,6 @@ lazy val todolist = project.in(file("Modules/Example Todolist")) Dependencies.loci.webrtc, Dependencies.loci.jsoniterScala, Dependencies.jsoniterScala, - jsAcceptUnfairGlobalTasks, TaskKey[File]("deploy", "generates a correct index.html for the todolist app") := { val fastlink = (Compile / fastLinkJS).value val jspath = (Compile / fastLinkJS / scalaJSLinkerOutputDirectory).value @@ -178,10 +177,9 @@ lazy val unitConversion = project.in(file("Modules/Example ReactiveLenses")) .enablePlugins(ScalaJSPlugin) .dependsOn(rescala.js) .settings( - scalaVersion_3, + scala3defaults, noPublish, Dependencies.scalatags, - jsAcceptUnfairGlobalTasks, TaskKey[File]("deploy", "generates a correct index.template.html for the unitconversion app") := { val fastlink = (Compile / fastLinkJS).value val jspath = (Compile / fastLinkJS / scalaJSLinkerOutputDirectory).value From a7464201ce5515634e8d1e9a2ab63a61d6fcf1ff Mon Sep 17 00:00:00 2001 From: ragnar Date: Fri, 8 Mar 2024 11:54:09 +0100 Subject: [PATCH 32/33] move lenses to extra package --- .../scala/rescala/{operator => extra/lenses}/LensBundle.scala | 3 ++- .../shared/src/main/scala/rescala/operator/Operators.scala | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) rename Modules/Reactives/shared/src/main/scala/rescala/{operator => extra/lenses}/LensBundle.scala (99%) diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala b/Modules/Reactives/shared/src/main/scala/rescala/extra/lenses/LensBundle.scala similarity index 99% rename from Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala rename to Modules/Reactives/shared/src/main/scala/rescala/extra/lenses/LensBundle.scala index f8aec752a..f62d48cce 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/LensBundle.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/extra/lenses/LensBundle.scala @@ -1,6 +1,7 @@ -package rescala.operator +package rescala.extra.lenses import rescala.core.* +import rescala.operator.Operators trait LensBundle { self: Operators => diff --git a/Modules/Reactives/shared/src/main/scala/rescala/operator/Operators.scala b/Modules/Reactives/shared/src/main/scala/rescala/operator/Operators.scala index c448c8225..acd190e8a 100644 --- a/Modules/Reactives/shared/src/main/scala/rescala/operator/Operators.scala +++ b/Modules/Reactives/shared/src/main/scala/rescala/operator/Operators.scala @@ -1,5 +1,7 @@ package rescala.operator +import rescala.extra.lenses.LensBundle + /** To support virtual State types, everything is put into the bundle traits. * But because the operators all have cyclic dependencies to each other, * we need this combining bundle, which all other operator bundles use as a self type From 3a1241263aa7f31c3df708ea02510660578c97c2 Mon Sep 17 00:00:00 2001 From: ragnar Date: Fri, 8 Mar 2024 20:40:11 +0100 Subject: [PATCH 33/33] update changelog --- Changelog.scim | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Changelog.scim b/Changelog.scim index 739943843..823ad6d19 100644 --- a/Changelog.scim +++ b/Changelog.scim @@ -1,5 +1,10 @@ = Changelog +== v0.35.1 +date = 2024-03-08 + +• update Scala version to 3.3.3 due to incompatible release + == v0.35.0 date = 2024-02-24