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
Futuresuch asflatMapflatMap, 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.collectrecoverfallbackTozipandThen
- Create a promise and register the complete method to current
futures, then return a new Future of this promise.
Promiseacts like a mailbox, to compose a new future, the old future has to put the result, which is a Try[T], into the promise bycompletemethod. Then the new future will call its callback when thecompleteof 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
DefaultPromiseis actually extends theFuturetrait. 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...awaitgrammar.- 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.