Scala Study Notes
Table of Contents
1 Functional Programming on Coursera
1.1 Lambda function
- function is value,
val f = (x:Int) => x*x
One difference between
def f = ...
andval f = ...
is:def
is evaluated on call and returns a new function instance every time.val test: () => Int = { println("val") val r = util.Random.nextInt () => r } // returns the same result every time you call test(), // "val" will be printed after definition def test: () => Int = { println("def") val r = util.Random.nextInt () => r } // returns the difference results every time you call test(), // "def" will be printed every time you call it. val tt = test // acts like the first example val test = () => util.Random.nextInt def test = () => util.Random.nextInt // they are the same from behavior, you have to call the function by test() // the def declaration actually defined a function that returns a function. // if you type test without parenthesis, you will receive a function value. def test = util.Random.nextInt // this line directly assign the test function to the nextInt function, // you can call the function directly by name without the parenthesis
method/expression starts by
def
will be evaluated when you callobj.f
while those starts byval
will be evaluated once. In addition, if you declare aval
that assigns todef
, thedef
will be evaluated immediately, and the newval
acts like the first case in the examples above. - Abbreviation:
val l = List(1,2,3) l.map((x) => x*x) l.map( x => x*x) // ignore the parenthese l.map( _ % 2 == 0) // ignore the parameter, using the placeholder for expression l.map(f(_)) // using the placeholder for function l.map(f) // ignore the placeholder for function
1.2 Type hierachy
abstract class is like Java, trait is like interface in Java. However, trait can have parameters and defined methods. In addition, trait is not abstract class since it cannot has constructors.
- Logical problem: is List<Parent> the parent of List<Childe>?
It is not true for mutable collection.
var s = new Array[Man](new Man()) var t:Array[Human] = s t[0] = new Woman() var m:Man = s[0]// what is it? man or woman?
But we can use imutable collection.
- Covariant, Contvariant, Invariant
- Covariant: defined by Class<+T>, Class<Parent> is the parent of Class<Child>
- Contvariant: defined by Class<-T>, Function<Parent> is the child of Function<Child>
The principle is: child type can do anything that can be done by parent type.
- Check Rules and Boundary
To avoid conflict, the compile force the +T can only be used for returned type, the -T can only be used for argument type. A method to by pass this problem is using boundary.
class List<+T>{ def prepend[U >: T](U elem) }
Notice that in generic type definition, one can use
A>:B
andA<:B
to add constraint to the meta type.
1.3 Pattern match
Case class enables you to make classes with different parameters
abstract class CodeTree case class Fork(left: CodeTree, right: CodeTree, chars: List[Char], weight: Int) extends CodeTree case class Leaf(char: Char, weight: Int) extends CodeTree
For the classes listed above, you cannot directly access to any
paramter (field) of Fork
or Leaf
. You have to use case
Fork(a,b,_,_)
to access to the paramters.
case
match can also be used in lambda function as a short
version.
List.map(p => p._1 * p._2) List.map({case (x,y) => x * y}) //List.map{case (x,y) => x * y} also works
Option calsss used for the condition that the returned type can be empty/null, etc.
If you want to match a variable in context, you have to weither use
a capitalized variable name Variable
or wrap it by backticks
`variable`
.
1.4 Implicit
1.4.1 Implicit Parameter
Implicit parameter looks similar to default parameter, but their principles are completely different. Default parameter has a default value to be used when the parameter is not given. Implicit parameter will search the context to get an appropriate value/variable as parameter.
abstract class Logger {def log(s: String)} class FileLogger extends Logger { def log(s: String) {println("Log in file: " + s)} } class StdoutLogger extends Logger { def log(s: String) {println("Stdout: " + s)} } def Add(a: Int, b: Int)(implicit logger: Logger) { val sum = a + b logger.log("%d + %d = %d".format(a, b, sum )) } implicit val log = new FileLogger Add(1,2) Add(2,3)(new StdoutLogger) //you may do it explicitly
If there is no implicit val log = new FileLogger
, the compiler
will report error. It is similar to injection by type.
Question: what if there are two possible candidates in the context?
1.4.2 Implicit Function
1.4.3 Implicit class
1.5 Collections
- List is a chain while Vector is a tree that each node is an array
that contains 32 elements. Vector groups in depth slowly
log_{32}(N)
. Important functions:map
,filter
,reduceRight
,reduceLeft
,foldRight
,foldLeft
,flatten
,flatMap
,take
,drop
,takeWhile, =dropWhile
,span
,group
,groupBy
.Sequence as parameter list is represented by
this(par: T*)
. - Map.
- The
+
and++
operations overwrite the existing
key.
- Initialized by
Map(k1->v1, k2->v2, k3->v3)
- Set default value by
withDefaultValue
, this function returns a new map (everything in this course are immutable).
- The
1.6 Stream, Iterator, Generator and lazy
1.6.1 Stream
Stream, acts like sequence, but it does not evaluate until be called. It can be used to ignore unecessary computation and coding infinite concepts.
val fibo:Stream[Int] = 1 #:: 1 #:: fibo.zip(fibo.tail).map(x=>x._1+x._2) println(fibo.take(10).toList) def from(n: Int): Stream[Int] = n #:: from(n+1) // infinite integer stream starts from n // recursive stream def sieve(s: Stream[Int]): Stream[Int] = s.head #:: sieve(s.tail filter (_ % s.head !=0)) // stream of primes 质数 val primes = sieve(from(2)) primes.take(10).toList // the first 10 primes starts from 2.
Three ways of using stream
- Transform from other collections, then you use functions like map, etc. to generate new stream.
elem #:: Stream
- Transform from iterator, use for loop to create an iterator with what you want and then convert it into a stream.
Because Stream has consistent API as List/Seq/Vector, you can use it as if you have a collection that contains everything.
1.6.2 Iterator
The difference between stream and iterator is stream memories the values while iterator doesn't.
Iterator can be used in for-expression. For-expression can also use pattern match.
for{ pattern(x) <- seq; pattern2(y) = x(k); ...} yield ...
As show in the example above, you can apply pattern to loop on the elements of sequence, you can even add some simple statements in the for-expression. It is equivalent to:
seq.withFilter({case pattern(x) => true; case _ => false}) .map({case pattern(x) => x(k)}) .withFilter({case pattern2(y) => true; case _ => false}) .map({case pattern2(y) => y}) ...
The function withFilter
returns a FilterMonadic
, which
provides four functions flatMap
, map
, foreach
,
withFilter
. It works like list of call backs in programming
point of view.
From wikipedia:
/In functional programming, a monad is a structure that represents computations defined as sequences of steps: a type with a monad structure defines what it means to chain operations, or nest functions of that type together.
1.6.3 lazy val
lazy val
is only evaluated once when called. You can also define
a lazy parameter by def myFUnc[T](param: => T)
, then the
parameter will be evaluated when it is called in myFunc
if the
param is returned by an expression/function.
2 Principles of Reactive Programming on Coursera
2.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.2 ScalaCheck & ScalaTest
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.
2.2.1 Tutorial of ScalaCheck
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.2 ScalaTest
In the example provided by the oneline course, they used JUnit,
ScalaTest and ScalaCheck together, which is much more complext
than the tutorial. 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.
@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) } }
- 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 typeJUnitRunner
. In fact, any class extendsRunner
is acceptabel. Notice that functionclassOf
acts the same asobj.getClass()
. - The annotation uses class of
JUnitRunner
as parameter. This class is provided by scala-test framework. TODO org.scalatest.FunSuite
andorg.scalatest.prop.Checkers
.