These are my notes and study guide how I approach studying Reactive Programming
.
The basic principle of reactive programming is:
Reacting to sequence of events that happen in time
, and, using these patterns to, build software systems that are more robust, more resilient, more flexible and better positioned to meet modern demands. -- Reactive Manifesto
In computing, reactive programming is a
programming paradigm oriented around data flows and the propagation of change
. This means that it should be possible to express static or dynamic data flows with ease in the programming languages used, and that the underlying execution model will automatically propagate changes through the data flow. -- Wikipedia
- Typesafe - Going Reactive in Java with Typesafe Reactive Platform
- Typesafe - Deep Dive into the Typesafe Reactive Platform - Akka and Scala
- Typesafe - Deep Dive into the Typesafe Reactive Platform - Activator and Play
- Typesafe - What Have The Monads Ever Done For Us with Dick Wall
- Typesafe - Deep Dive into the Typesafe Reactive Platform - Ecosystem and Tools
- What does it mean to be Reactive? - Erik Meijer
- Scalaz - Scalaz a Scala library for functional programming.
- Learning Scalaz
Stream processing is a different paradigm to the Actor Model or to Future composition, therefore it may take some careful study of this subject until you feel familiar with the tools and techniques. -- Akka Streams Documentation
- Akka Streams Documentation 1.0-RC1
- Quick Start - Reactive Tweets
- Akka Streams API 1.0-RC1
- Design Principles behind Reactive Streams
- Streams Cookbook
- Overview of built-in stages and their semantics
- Reactive Streams
Futures provide a nice way to reason about performing many operations in parallel– in an efficient and non-blocking way. The idea is simple, a Future is a sort of a placeholder object that you can create for a result that does not yet exist. Generally, the result of the Future is computed concurrently and can be later collected. Composing concurrent tasks in this way tends to result in faster, asynchronous, non-blocking parallel code. -- ScalaDocs
- Scala - Async await
- Scala - Futures
- Akka - Futures
- The Neophyte's Guide to Scala Part 8 - Welcome to the Future
- The Neophyte's Guide to Scala Part 9 - Promises and Futures in Practice
- Ian Irvine - Notes (2014) - Monads and Effects
- Ian Irvine - Notes (2014) - Latency as an Effects
- Ian Irvine - Notes (2014) - Combinators on Futures
- Ian Irvine - Notes (2014) - Composing Futures
- Ian Irvine - Notes (2014) - Promises
Functional reactive programming (FRP) is a programming paradigm for reactive programming (asynchronous dataflow programming) using the building blocks of functional programming (e.g. map, reduce, filter). FRP has been used for programming graphical user interfaces (GUIs), robotics, and music, aiming to simplify these problems by explicitly > modeling time. -- Wikipedia
I would advice reading / viewing the resources below to get a good idea on what Functional Reactive Programming is. The model we use this week is push based, in which systems take events and push them through a 'signal' network to achieve a result. The basic idea of FRP that we focus on this week is that events are combined into 'signals' that always have a current value, but change discretely. The changes are event-driven. But instead of having an event handler that returns Unit, (like the onClick handler and such), it returns a value.
FRP in a nutshell (for now at least):
When we do an assignment in Scala, the following happens:
scala> var a = 1
a: Int = 1
scala> var b = 2
b: Int = 2
scala> var c = a + b
c: Int = 3
scala> a = 2
a: Int = 2
scala> c
res1: Int = 3
scala> var c = a + b
c: Int = 4
As we can see, the value of c
did not change, when we changed the value of a
from 1
to 2
. This is normal behavior
because we have expressed the relationship at one point in the execution of the program.
But what if, c
would change when we changed the value of a dependent value like a
. This would mean that there is a
dependency
created between c
, a
and b
that expresses how these values will relate over time. So the basic idea is
that c
will change when we change either a
and/or b
.
The following should work:
def tweetRemainingCharsCount(tweetText: Signal[String]): Signal[Int] =
Signal(MaxTweetLength - tweetLength(tweetText()))
def colorForRemainingCharsCount(remainingCharsCount: Signal[Int]): Signal[String] =
Signal {
remainingCharsCount() match {
case count if (0 to 14).contains(count) => "orange"
case count if count < 0 => "red"
case _ => "green"
}
}
Please first try it yourself, then if you wish, verify.
def computeDelta(a: Signal[Double], b: Signal[Double], c: Signal[Double]): Signal[Double] =
Signal {
Math.pow(b(), 2) - (4 * a() * c())
}
def computeSolutions(a: Signal[Double], b: Signal[Double], c: Signal[Double], delta: Signal[Double]): Signal[Set[Double]] =
Signal {
delta() match {
case discriminant if discriminant < 0 => Set()
case discriminant if discriminant == 0 => Set(calcLeft(a(), b(), c()))
case discriminant => Set(calcLeft(a(), b(), c()), calcRight(a(), b(), c()))
}
}
def calcLeft(a: Double, b: Double, c: Double): Double =
(-1 * b + Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a)
def calcRight(a: Double, b: Double, c: Double): Double =
(-1 * b - Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a)
Please first try it yourself, then if you wish, verify.
def computeValues(namedExpressions: Map[String, Signal[Expr]]): Map[String, Signal[Double]] = {
namedExpressions.mapValues { expr =>
Signal(eval(expr(), namedExpressions))
}
}
def eval(expr: Expr, references: Map[String, Signal[Expr]]): Double = {
expr match {
case Literal(v) => v
case Ref(name) => eval(getReferenceExpr(name, references), references - name)
case Plus(aExpr, bExpr) => eval(aExpr, references) + eval(bExpr, references)
case Minus(aExpr, bExpr) => eval(aExpr, references) - eval(bExpr, references)
case Times(aExpr, bExpr) => eval(aExpr, references) * eval(bExpr, references)
case Divide(aExpr, bExpr) => eval(aExpr, references) / eval(bExpr, references)
case _ => Double.MaxValue
}
}
/** Get the Expr for a referenced variables.
* If the variable is not known, returns a literal NaN.
*/
private def getReferenceExpr(name: String, references: Map[String, Signal[Expr]]): Expr = {
references.get(name).fold[Expr](Literal(Double.NaN)) {
exprSignal => exprSignal()
}
}
- What is the difference between view, stream and iterator?
- Wikipedia - Functional Reactive Programming
- Functional Reactive Animation - Elliott / Hudak (PDF)
- Deprecating the Observer Pattern - Odersky / Maier (PDF)
- Stackoverflow - What happened to scala.react?
- Reactive Design Patterns - Kuhn - Chapter 1 (PDF)
- Functional Reactive Programming - Blackheath - Chapter 1 (PDF)
- Reactive Web Applications with Play - Bernhardt - Chapter 1 (PDF)
- Reactive Application Development - Devore - Chapter 1 (PDF)
- Functional and Reactive Domain Modeling - Ghosh - Chapter 1 (PDF)
- An Introduction to Functional Reactive Programming
- Functional Reactive Programming in Elm - Evan Czaplicki
- Building Reactive Apps - James Ward
- What does invariant mean?
- The Neophyte's Guide to Scala Part 12 - Type Classes
- Learn yourself Haskell - Functors, Applicative Functors and Monoids
- Learn yourself Haskell - A fistful of Monads
- What is a Priority Queue?
- What is a Binary Heap?
- Algorithms with Attitude - Introduction to Binary Heaps
- Algorithms with Attitude - Binary Heaps for Priority Queues
- Algorithms with Attitude - Optimized Heapify
- Algorithms with Attitude - HeapSort
- Algorithms with Attitude - Lineair Time BuildHeap
- Typeclasses in Scala with Dan Rosen
- Vladimir Kostyukov's Scalacaster: algorithms and data structures in Scala
For testing, you want to insert two values into the heap
, for example with:
forAll { (x: Int, y: Int) =>
}
When you add the values into the heap and search for the minimum findMin
, it would be handy
to know whether x
or y
is the smallest, the following will help:
scala> def order = scala.math.Ordering.Int
order: math.Ordering.Int.type
scala> order.min(2,1)
res0: Int = 1
The heap has the method ord
that does the same.
To generate a heap, you can use the following code:
lazy val genHeap: Gen[H] = for {
n <- arbitrary[Int]
h <- oneOf(empty, genHeap)
} yield insert(n, h)
Did you notice the following? We have three methods, isEmpty
, findMin
and deleteMin
. When you combine these
methods you can iterate
over the heap until its empty and put the contents into a list:
def heapToList(h: H): List[Int] =
if(isEmpty(h)) Nil else findMin(h) :: heapToList(deleteMin(h))
Lists can be sorted:
scala> val xs = List(2,3,1,5,6,2,3,4,0,1,2)
xs: List[Int] = List(2, 3, 1, 5, 6, 2, 3, 4, 0, 1, 2)
scala> xs.sorted
res0: List[Int] = List(0, 1, 1, 2, 2, 2, 3, 3, 4, 5, 6)
Lists can also be compared:
scala> List(1, 2) == List(1, 2)
res0: Boolean = true
scala> List(1, 2) == List(2, 1)
res1: Boolean = false
scala> List(1, 2) == List(2, 1).sorted
res2: Boolean = true
scala> List(2, 1).sorted == List(2, 1).sorted
res3: Boolean = true