Principles of Reactive Programming on Coursera
Table of Contents
1 Monad
Honestly, I don’t understand it. But I collect my thoughts about it here. First, the monda is used to chain operations and if the first (preceding) operation fails, the whole chain stops. An example that I understand is the usage of for-expression with pattern match. If the pattern matches, go next operation, else skip this element.
Another point of view is about the unit operation. A monad flatMap with its unit operation will return the monad itself.
My conclusion:
A monad is a container that supports operation chain and follows
the monad laws. The basic operations of this container are
flatMap
and unit
, where flatMap
actually accepts a function
that maps each element to a container, then binds these containers
together; unit
returns a container given an element. Monad laws
guarantee the reliability of operation chain on these contains.
Monad seems like a design pattern that enables the operation chain
along with pattern match in functional programming. Because
flatMap
chain with different functions is equivalent to nested
flatMap
, the for-expression is a syntax sugar based on flatMap
chain. The advantage of this design pattern is avoding side effect.
Generator is created by yield
as well as
for-expression. Pay attention, there is no generator
class/type/trait. The genrator created through for-expression has
the same type of the sequence/iterator/stream used in
for-expression. That proves the for-expression is a syntax sugar of
monad types. So, guess what will happen when we create a
for-expression using both list and stream?
val fibo:Stream[Int] = 1 #:: 1 #:: fibo.zip(fibo.tail).map(x=>x._1+x._2) val l = List(1,2,3,4,5) val lsg = for{x <- l; fib <- fibo} yield (x,fib)// infinite loop, crash the software/your machine val slg = for{fib <- fibo; x <- l} yield (x,fib)// return a stream
Following the definition of Monad, the first mixture lsg
actually
makes l flatMap (x => fibo)
, which returns a List that tries to
expand infinite stream fibo
, hence block your machine. The second
mixture slg
returns a Stream that expand the list l
, hence,
works well. Besides, I have to clarify one thing: different monads
demonstrated above all accept GenTraversableOnce
as parameter and
return monad of their own type. That is why they can be mixed
together and the first expression decides the type of the final
output of for-expression.
2 ScalaCheck & ScalaTest & JUnit
In the example provided by the oneline course, they used JUnit,
ScalaTest and ScalaCheck together. In their example, the class
of Properties
is in src/main/scala
and is called by another
class defined under src/test/scala
. In the class under test
folder, many instances of the Properties
class are created to
check different children classes of the target class.
abstract class QuickCheckHeap extends Properties("Heap") with IntHeap { property("min1") = forAll { a: Int => val h = insert(a, empty) findMin(h) == a } lazy val genHeap: Gen[H] = for { a <- arbitrary[Int] h <- oneOf(value(empty), genHeap) } yield insert(a, h) implicit lazy val arbHeap: Arbitrary[H] = Arbitrary(genHeap) }
@RunWith(classOf[JUnitRunner]) class QuickCheckSuite extends FunSuite with Checkers { def checkBogus(p: Prop) { var ok = false try { check(p) } catch { case e: TestFailedException => ok = true } assert(ok, "A bogus heap should NOT satisfy all properties. Try to find the bug!") } test("Binomial heap satisfies properties.") { check(new QuickCheckHeap with BinomialHeap) } test("Bogus (1) binomial heap does not satisfy properties.") { checkBogus(new QuickCheckHeap with Bogus1BinomialHeap) } test("Bogus (2) binomial heap does not satisfy properties.") { checkBogus(new QuickCheckHeap with Bogus2BinomialHeap) } test("Bogus (3) binomial heap does not satisfy properties.") { checkBogus(new QuickCheckHeap with Bogus3BinomialHeap) } test("Bogus (4) binomial heap does not satisfy properties.") { checkBogus(new QuickCheckHeap with Bogus4BinomialHeap) } test("Bogus (5) binomial heap does not satisfy properties.") { checkBogus(new QuickCheckHeap with Bogus5BinomialHeap) } }
2.1 Tutorial of ScalaCheck
It is a tool for property-based testing for scala. It has NO
external dependencies and integrated in the test frameworks
ScalaTest. It can also be used standalone, with its built-in test
runner.
First, create a class that extends class
org.scalacheck.Properties
with the name of data object that you
want to test. It is used for the library to generate test data to
test your algorithm.
Second, create test case by
property("NAME_OF_FUNCTION") = forAll{
CONTAINER_OF_DATA => TEST_CASE_WITH_TARGET_FUNCTION
}
forAll
is the function org.scalacheck.Prop.forAll
.
Third, to run the tests, you can put the properties in
src/test/scala
then use test task to check them.
2.2 ScalaTest
This is a test framework that integrates all together. ScalaTest
provides many test styles: FunSuit
, FlatSpec
, FuncSpec
,
WordSpec
, FreeSpec
, Spec
, PropSpec
, FeatureSpec
. They
are mainly different on syntax styles. It is recommanded that the
user creates a base class that extends all the required features
(as a subset of all the provided features) and extends this base
class through everywhere of the project to make the style
consistent.
2.2.1 With JUnit
This class uses the junit framework for unittest. JUnit will
invoke the class with @RunWith
annotation (extend by type
hierarchies) to run the tests. The annotation parameter in the
example is a class of the type JUnitRunner
. In fact, any class
extends Runner
is acceptabel. Notice that function classOf
acts the same as obj.getClass()
.
Maven Running tests with command mvn test
. In fact, you
cannot run the tests with the example above. Solution is to
change the name QuickCheckSuite
to QuickCheckSuiteTest
or
TestQuickCheckSuite
or QuickCheckSuiteTestCase
to run the
tests. Even I did not explicitly specify plugin surefire
, maven
uses this plugin to run test with my configuration. By default,
maven following name convertions when looking for tests to
run. So I have to change the name or modify the surefire
configuration to apply new name convertion rules.
The annotation uses class of JUnitRunner
as parameter. This
class is provided by scala-test framework that connect
scala-test and junit.
- As mentioned on ScalaTest Maven Plugin, you can run tests without this annotation by using this plugin. TODO
2.2.2 With ScalaCheck
To work with ScalaCheck, you have to mix the trait
org.scalatest.prop.Checkers
to your test class. Checkers
class contains check
method to perform ScalaCheck property
checks, which are provided in the class QuickCheckHeap
in the
example above. In fact, you can also use the check
method in
JUnit directly. This method will generate a large number of
possible inputs to look for an input that the property does not
hold.
3 Async
3.1 Exception => Future
Exception is handled by Try[T], which is used as return type like Option. But Try[T] maches Success with result or Failure with throwable. As Try[T] is also a monad, operations of element T are usually propagated to monad methods of Try[T] to get a new Try[U], map Try[T] by f should return a new Try[f(T)].
Future[T] is a container of Try[T]. User provide operations/callbacks that deal with Try[T] and future will call them at certain time. Future and Try form a nested monad, monad operations are propagated and you cannot see direct operation on T in the source code of scala library. But take it easy, usually, you won’t have to do it yourself.
To get/compose a future (that can be our job), therer are four methods:
- Use the member function of a
Future
such asflatMap
flatMap
, binary bind operation of futures. Register promise to this when failed of exception of call funtion; register promise to that when successed.onComplete
, parameter is a functionTry[T] => U
. This is a funtamental funciton ofmap
,foreach
,failed
, etc. It propagate the Try mechanism.collect
recover
fallbackTo
zip
andThen
- Create a promise and register the complete method to current
futures, then return a new Future of this promise.
Promise
acts like a mailbox, to compose a new future, the old future has to put the result, which is a Try[T], into the promise bycomplete
method. Then the new future will call its callback when thecomplete
of promise is called.In my opinion, promise can be merged with feature as one unit. Because it is more straight to think in the way that: feature calls the callbacks when it is completed. In fact, the
DefaultPromise
is actually extends theFuture
trait. Of course, the designers of scala have their proper reason.Currently, I think promise is used in race (concurrent processing of futures results) or to provide a new future for initailization of fold operations.
- Use
for-expression
, which is a syntax sugar offlatMap
- Use
async...await
grammar.- The scala implementation of await is translated to onComplete by the macro.
- await method throw directly the excepetion when future failed.
3.2 Iterable => Observable
Try
and Future
are dual, so as Iterable
and
Observable
. For iterable, you can get an iterator and using
hasNext
and next
to get elements. For Obserable
, you use
Subscribe
and onNext
, onComplete
to get elements.
flatten
after map
returns an observable that merges several
streams randomly. concat
after map
returns an observable, in
which, elements of each stream are grouped together sequentially
as well as the order of the streams.
Don’t understand well the way of creating observable with a function from Observer to Subscription -_-!
3.2.1 Subject
It works like promise, which is used as bridge or chain between Obsever and Observable. Four types of subjects are provided:
- PublishSubject: send current value.
- BehaviorSubject: cache the latest output.
- AsyncSubject: cache the final output. You only get the final output.
- ReplaySubject: cache the whole history.
3.2.2 Notification
Like Try
3.2.3 Subscription
a handle that enables process to stop receiving messages from Observable.