-
-
Save p-pavel/964cab474f3f92ab5a00430253556626 to your computer and use it in GitHub Desktop.
trait IncrementDecrement[F[_]] { // Входы/выходы приложения инкремента-декремента | |
val increment: Stream[F,Unit] // Поток заявок на инкремент | |
val decrement: Stream[F,Unit] // Поток заявок на декремент | |
val total: Pipe[F,Int, INothing] // Переработка текущих значений в чистые побочные эффекты | |
} | |
object Logic { | |
import scala.concurrent.duration._ | |
// Как работать с IncrementDecrement | |
def run[F[_]:Concurrent: Timer](app: IncrementDecrement[F]): Stream[F,INothing] = { | |
val changes: Stream[F, Int] = app.increment.map(_ ⇒ 1) merge app.decrement.map(_ ⇒ -1) merge Stream(1).covary[F].repeat.metered(5.seconds) | |
val output : Stream[F, Int] = changes.scan(0)(_+_).takeWhile(_ < 100) | |
output through app.total | |
} | |
} | |
trait UIUtils { // Для удобства связи fs2 и JavaFX | |
/** Преобразуем события в поток */ | |
protected def attachHandler[F[_],A <: Event](setHandler: EventHandler[A] ⇒ Unit)(implicit F: ConcurrentEffect[F]): Stream[F,A] = | |
Stream.eval(Queue.unbounded[F,A]).flatMap{queue ⇒ | |
setHandler(ev ⇒ F.toIO(queue.enqueue1(ev)).unsafeRunAsyncAndForget()) | |
Stream.repeatEval(queue.dequeue1) | |
} | |
/** Преобразуем поток обновлений типа A в поток чистых эффектов | |
@todo chunk updates */ | |
protected def updatePipe[F[_],A](set: A ⇒ Unit)(implicit F:ConcurrentEffect[F]): Pipe[F,A,INothing] = | |
_.evalMap(a ⇒ F.delay(Platform.runLater(() ⇒ set(a)))).drain | |
def run(effects: Stream[IO,INothing]): Unit = effects.compile.drain.unsafeRunAsyncAndForget() | |
// Среда выполнения для IO | |
implicit val sched: ContextShift[IO] = IO.contextShift(ExecutionContext.global) | |
implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global) | |
} | |
class UI extends javafx.application.Application with UIUtils { | |
private val incButton = new Button("Increment") | |
private val decButton = new Button("Decrement") | |
private val label = new Label ("0") | |
private val hBox = new HBox(5, incButton, decButton, label) | |
def incDec[F[_]: ConcurrentEffect]: IncrementDecrement[F] = new IncrementDecrement[F] { | |
override val increment: Stream[F, Unit] = attachHandler(incButton.setOnAction).map(_ ⇒ ()) | |
override val decrement: Stream[F, Unit] = attachHandler(decButton.setOnAction).map(_ ⇒ ()) | |
val rotatePipe:Pipe[F,Int,INothing] = updatePipe(a ⇒ hBox.setRotate(a)) | |
val labelPipe: Pipe[F,Int,INothing] = _.map(_.toString).through(updatePipe(label.setText)) | |
override val total: Pipe[F, Int, INothing] = | |
_ | |
.broadcastThrough(rotatePipe, labelPipe) | |
.onFinalize( Sync[F].delay(System.exit(0))) | |
} | |
override def start(primaryStage: Stage): Unit = { | |
run(Logic.run(incDec[IO])) | |
val scene = new Scene(hBox) | |
primaryStage.setScene(scene) | |
primaryStage.show() | |
} | |
} |
starred, forked. Yo da man.
You may also find interesting to look at my new project https://github.com/winitzki/ui - currently at the stage of a very early proof-of-concept code.
I'm about to hide my GitHub projects :) all of 'em were kind of discussions that lead mostly nowhere. I didn't expect that somebody will look at them :)
Thanks!
The implementation of UI via streams is elegant from the theoretical point of view but not very practical. Compare the code in this gist
with the amount of code that implements essentially the same functionality (increment/decrement buttons) using the Elm architecture: ExampleSimpleElmProgram.scala
The implementation of UI via streams is elegant from the theoretical point of view but not very practical. Compare the code in this
gist
with the amount of code that implements essentially the same functionality (increment/decrement buttons) using the Elm architecture: ExampleSimpleElmProgram.scala
Again, I can't remember how this gist came to life :) Probably some proof of concept or something. Thanks for the link!
You may also find interesting to look at my new project https://github.com/winitzki/ui - currently at the stage of a very early proof-of-concept code.