Functional Programming on Coursera
Table of Contents
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
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.
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`
.
4 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
5 Stream, Iterator, Generator and lazy
5.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.
5.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.
5.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.