-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handler/Sink refactor, added Pipe trait #91
Changes from 1 commit
6c1de88
ddc89b9
7c58cb4
c7bbad7
2392bec
565b318
6cb14cb
5e1ba29
4846f0f
d21fceb
669583c
279704b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package outwatch | ||
|
||
import rxscalajs.Observable | ||
|
||
import scala.language.implicitConversions | ||
|
||
trait Pipe[-I, +O] { | ||
def sink: Sink[I] | ||
|
||
def source: Observable[O] | ||
|
||
def mapSink[I2](f: I2 => I): Pipe[I2, O] = Pipe(sink.redirectMap(f), source) | ||
|
||
def collectSink[I2](f: PartialFunction[I2, I]): Pipe[I2, O] = Pipe(sink.redirect(_.collect(f)), source) | ||
|
||
def mapSource[O2](f: O => O2): Pipe[I, O2] = Pipe(sink, source.map(f)) | ||
|
||
def collectSource[O2](f: PartialFunction[O, O2]): Pipe[I, O2] = Pipe(sink, source.collect(f)) | ||
|
||
def filterSource(f: O => Boolean): Pipe[I, O] = Pipe(sink, source.filter(f)) | ||
|
||
def mapPipe[I2, O2](f: I2 => I)(g: O => O2): Pipe[I2, O2] = Pipe(sink.redirectMap(f), source.map(g)) | ||
|
||
def collectPipe[I2, O2](f: PartialFunction[I2, I])(g: PartialFunction[O, O2]): Pipe[I2, O2] = Pipe( | ||
sink.redirect(_.collect(f)), source.collect(g) | ||
) | ||
} | ||
|
||
object Pipe { | ||
implicit def toSink[I](pipe: Pipe[I, _]): Sink[I] = pipe.sink | ||
implicit def toSource[O](pipe: Pipe[_, O]): Observable[O] = pipe.source | ||
|
||
private[outwatch] def apply[I, O](_sink: Sink[I], _source: Observable[O]): Pipe[I, O] = new Pipe[I, O] { | ||
def sink: Sink[I] = _sink | ||
def source: Observable[O] = _source | ||
} | ||
|
||
implicit class FilterPipe[I, +O](pipe: Pipe[I, O]) { | ||
|
||
def filterSink(f: I => Boolean): Pipe[I, O] = Pipe(pipe.sink.redirect(_.filter(f)), pipe.source) | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package outwatch | ||
|
||
import rxscalajs.Observable | ||
|
||
package object advanced { | ||
|
||
implicit class TransformPipe[-I, +O](pipe: Pipe[I, O]) { | ||
|
||
def transformSink[I2](f: Observable[I2] => Observable[I]): Pipe[I2, O] = Pipe(pipe.sink.redirect(f), pipe.source) | ||
|
||
def transformSource[O2](f: Observable[O] => Observable[O2]): Pipe[I, O2] = Pipe(pipe.sink, f(pipe.source)) | ||
|
||
def transformPipe[I2, O2](f: Observable[I2] => Observable[I])(g: Observable[O] => Observable[O2]): Pipe[I2, O2] = | ||
Pipe(pipe.sink.redirect(f), g(pipe.source)) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,14 @@ | ||
package outwatch.dom | ||
|
||
import cats.effect.IO | ||
import org.scalajs.dom.MouseEvent | ||
import org.scalajs.dom.raw.{ClipboardEvent, DragEvent, KeyboardEvent} | ||
import outwatch.Sink | ||
import outwatch.dom.helpers.InputEvent | ||
import rxscalajs.Observable | ||
import cats.effect.IO | ||
|
||
/** | ||
* Trait containing event handlers, so they can be mixed in to other objects if needed. | ||
*/ | ||
trait Handlers { | ||
type Handler[A] = Observable[A] with Sink[A] | ||
|
||
def createInputHandler() = createHandler[InputEvent]() | ||
def createMouseHandler() = createHandler[MouseEvent]() | ||
|
@@ -22,7 +19,7 @@ trait Handlers { | |
def createBoolHandler(defaultValues: Boolean*) = createHandler[Boolean](defaultValues: _*) | ||
def createNumberHandler(defaultValues: Double*) = createHandler[Double](defaultValues: _*) | ||
|
||
def createHandler[T](defaultValues: T*): IO[Handler[T]] = Sink.createHandler[T](defaultValues: _*) | ||
def createHandler[T](defaultValues: T*): IO[Handler[T]] = Handler.create[T](defaultValues: _*) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know, this was there before. but why have two methods for creating a handler? This is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea. Deprecating the old one sounds like a good idea. What about the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add them to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think prefer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right! |
||
} | ||
|
||
object Handlers extends Handlers |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,15 @@ package object dom extends Attributes with Tags with Handlers { | |
type VNode = IO[VNode_] | ||
type VDomModifier = IO[VDomModifier_] | ||
|
||
type Observable[+A] = rxscalajs.Observable[A] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really like the direction of outwatch becoming frp-backend agnostic. 👍 |
||
val Observable = rxscalajs.Observable | ||
|
||
type Sink[-A] = outwatch.Sink[A] | ||
val Sink = outwatch.Sink | ||
|
||
type Handler[A] = outwatch.Handler[A] | ||
val Handler = outwatch.Handler | ||
|
||
implicit def stringNode(string: String): VDomModifier = IO.pure(StringNode(string)) | ||
|
||
implicit def optionIsEmptyModifier(opt: Option[VDomModifier]): VDomModifier = opt getOrElse IO.pure(EmptyVDomModifier) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import cats.effect.IO | ||
import outwatch.Sink.SubjectSink | ||
import outwatch.advanced._ | ||
|
||
package object outwatch { | ||
|
||
type >-->[-I, +O] = Pipe[I, O] | ||
val >--> = Pipe | ||
|
||
type Handler[A] = Pipe[A, A] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we do this, we can call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I see what you mean. How do you feel about changing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, I think the solution below is probably the best :) |
||
|
||
object Handler { | ||
|
||
/** | ||
* This function also allows you to create initial values for your newly created Handler. | ||
* This is equivalent to calling `startWithMany` with the given values. | ||
* @param seeds a sequence of initial values that the Handler will emit. | ||
* @tparam T the type parameter of the elements | ||
* @return the newly created Handler. | ||
*/ | ||
def create[T](seeds: T*): IO[Handler[T]] = create[T].map { handler => | ||
if (seeds.nonEmpty) { | ||
handler.transformSource(_.startWithMany(seeds: _*)) | ||
} | ||
else { | ||
handler | ||
} | ||
} | ||
|
||
def create[T]: IO[Handler[T]] = IO { | ||
val sink = SubjectSink[T]() | ||
Pipe(sink, sink) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why we need an advanced package, or how these methods could be unsafe. I find these methods especially useful to write correct, bug-free code.
Do you have an example of an unsafe usage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's safe as long the original
Observable
is transformed, but a differentObservable
could be returned.For example, let's say one would like to emit two elements every time a button is clicked and writes this (incorrect) code:
However, this doesn't work as expected because the handler's observable is closed immediately on creation and the
click
handler doesn't do anything.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is that unsafe? The whole observable is replaced by another one. We cannot protect the user to use the wrong operators. Am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Safe was not the correct term to use. What I was trying to say is that in the above example the button click doesn't do anything because the observable is completed after emitting (1, 2) , which is probably not what is intended, instead it should have been:
Often the
transform...
methods will require the use of higher order observables. There's nothing wrong with that, but they are easier to use incorrectly, which is why I placed them under theoutwatch.advanced._
import. It also aligns with one of the Outwatch goal: "Removing or restricting the need for Higher Order Observables".There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But aren't we already importing higher-order Observables from https://lukajcb.github.io/rxscala-js/latest/api/rxscalajs/Observable.html?
@LukaJCB ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've actually changed my mind. We should place the
transform...
methods in theHandlerOps
trait. They are no more "advanced" thanSink.redirect
, which are available to the user.