Skip to content

Instantly share code, notes, and snippets.

@kaisellgren
Last active January 7, 2016 03:03
Show Gist options
  • Save kaisellgren/11393718 to your computer and use it in GitHub Desktop.
Save kaisellgren/11393718 to your computer and use it in GitHub Desktop.
Academic Scala implementations of Functional Reactive Programming -- different Signal[A] and foldp[A, B] implementations as examples
// foldp[A, B] impl. for Signal[A], i.e. the STM -version.
// This function is defined as: foldp := (Signal<A>, (B, A) => B, B) => Signal<B>
def foldp[A, B](source: Signal[A], initial: B)(predicate: (B, A) => B): Signal[B] = {
val result = new Signal(initial)
source.listen({ ev: A =>
result.set(predicate(result.get, ev))
})
result
}
object Foo extends App {
val clicks = new Signal(0)
val totalClicks = foldp(clicks, 0) { (previous: Int, current: Int) => previous + 1}
clicks.set(0)
clicks.set(0)
println(totalClicks.get) // 2!
}
// How do I implement foldp[A, B] for SignalImmutable[A] (or SignalSM[A]) ?
// TODO: Does not work at all!
def foldpImmutable[A, B](source: SignalImmutable[A], initial: B)(predicate: (B, A) => B): SignalImmutable[B] = {
val result = SignalImmutable(initial)
SignalImmutable.listen({ ev: A =>
// Great, so how do I actually update ´result´?
SignalImmutable.set(predicate(SignalImmutable.get(result), ev))
})(source)
result
}
object Foo extends App {
val clicks = SignalImmutable(0)
val totalClicks = foldpImmutable(clicks, 0) {(previous: Int, current: Int) => previous + 1}
// This creates a new SignalImmutable, all hell breaks loose.
SignalImmutable.set(0)(clicks)
println(totalClicks.value)
}
// This first Signal[A] impl. uses STM (Ref — transactional references).
class Signal[A](initial: A) {
type SignalListener = Function[A, Unit]
val value = Ref(initial)
val listeners = Ref(Seq[SignalListener]())
def set(newValue: A): Unit = {
// Atomically change the value within a transaction, blocks the thread, but a not an issue since it's a simple operation.
// Finally return the current state/set of listeners and calls them.
atomic { implicit txn =>
value() = newValue
listeners()
}.foreach(_(newValue))
}
def get: A = atomic { implicit txn =>
value()
}
/** Sets a new listener. */
def listen(listener: SignalListener): Unit = atomic { implicit txn =>
listeners() = listeners() :+ listener
}
}
object Foo extends App {
val slot = new Signal(0)
slot.listen({ value =>
println(value) // Hooray! it prints 5!
})
slot.set(5)
}
// An immutable version called SignalImmutable[A]. Based on the concept of state transformations.
package object Foo { type SignalListener = Function[A, Unit] }
case class SignalImmutable[A](value: A, listeners: Seq[SignalListener] = Seq())
object SignalImmutable {
def set[A](newValue: A)(signal: SignalImmutable[A]): SignalImmutable[A] = {
signal.listeners.foreach(_(newValue))
signal.copy(value = newValue)
}
def get[A](signal: SignalImmutable[A]): A = signal.value
def listen[A](listener: SignalListener)(signal: SignalImmutable[A]): SignalImmutable[A] = {
signal.copy(listeners = signal.listeners :+ listener)
}
}
object Foo extends App {
// New instances after every state change. Ugly, yes.
val sig = SignalImmutable(1)
val sig2 = SignalImmutable.listen({v: Int => println(v)})(sig)
val sig3 = SignalImmutable.set(5)(sig2)
}
// This version is immutable and uses state monads.
package object Foo { type SignalListener = Function[A, Unit] }
case class SignalSM[A](value: A, listeners: Seq[SignalListener] = Seq())
object SignalSM {
def set[A](newValue: A): State[SignalSM[A], Unit] = State(signal => {
signal.listeners.foreach(_(newValue))
(signal.copy(value = newValue), ())
})
def get[A]: State[SignalSM[A], A] = State(signal => (signal, signal.value))
def listen[A](listener: SignalListener): State[SignalSM[A], Unit] = State(signal => {
(signal.copy(listeners = signal.listeners :+ listener), ())
})
}
object Foo extends App {
val state = for {
_ <- SignalSM.set(10)
_ <- SignalSM.listen({ value: Int => println(s"Here it is: $value")})
_ <- SignalSM.set(20)
s <- State.get
} yield s
state.exec(SignalSM(5))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment