scala notes
- 1. val and var
- 2. Classes and Objects
- 3. singleton objects
- 4. Operators
- 5. operatros are methods
- 6. Class
- 7. Inheritance
- 8. Build-in control structures
- 9. Function and Closures
- 10. Control Abstraction
- 11. Curring
- 12. By-name parameters
- 13. Traits
- 14. Imports
- 15. Assertions and Unit Testing
- 16. Case Classese && Pattern Matching && Extractors
- 17. Collections
- 18. List & Sets and Maps
- 19. collection hierarchy
- 20. Type Parameterization
- 21. variance annotations
- 22. Abstract Members
- 23. Implicit Conversions and Parameters
- 24. Annotations
- 25. Modular Programming Using Object
- 26. Actors and Concurrency
val and var
val: you could not reassign the variant, but whether the variant is mutable or not depends on its own type.
the parameters are vals( functional programming)
Classes and Objects
singleton objects
- a class and its companion object can access each other’s private members.
- singleton objects inheriting from classes and traits in Chp 13
- you should inherit from
Application
only when your program is relatively simple and single-threaded.
Operators
operatros are methods
- infix:
a + b
:(a).+(b)
- prefix:
+
,-
,!
and~
.(short hand ofunary_+
…) - postfix: methods that take no arguments
- operator associativity:
:
right ro left.
no matter what associativity an operator has, its operands are always evaluated left to right
Class
- the Scala compiler will place the code for the initializers of the fields into the primary constructor in the order in which they appear in the source code.
- abstact classes
a class with abstract memeber must iself be declared abstract. - using parameterless methods(when no parameter), it supports the uniform access principle, which says that client code should not be affected by a decision to implement an attribute as a field or method.
encouraged: define methods that take no parameters and have no side effects as parameterless methods.
Inheritance
- subtyping means that a value of the subclass can be used wherever a value of the superclass is required.
scala has two namespaces:
1. values(fields, methods, package, and singleton objects)
types(class and trait names)
so you could overriding a parameterless method with a field.
factory object
a factory object contains methods that construct other objects. Clients would then use these factory methods for object construction rather than constructing the objects directly withnew
Null
is a subclass of every reference class.Nothing
is a subtype of every other type.- value equality:
==
, reference equality:eq
. How to write an equality method ?
1. override `equals` in class `Any`: `override def equals(other: Any) = ...`
- override
hashCode
method - when
equals
andhashCode
are defined in terms of mutable fields, you may need more things to do. to deal with the equality relationships between class and its subclass, you need to add one more method
def canEqual(other: Any): Boolean
for non-final class. For the example bellow, if the subclassColoredPoint
overridescanEqual
, then the instance ofColoredPoint
andPoint
is not equal, otherwise, it can be equal to aPoint
instance.//one example
class Point(val x: Int, val y: Int) {
override def hashCode = 41*(41 + x) + y
override def equals(other: Any) = other match {case that: Point => (that canEqual this) && (this.x == that.x) && (this.y == that.y) case _ => false
}
def canEqual(other: Any) = other.isInstanceOf[Point]
}
`
- override
Build-in control structures
- Try to challenge
while
loops in your code in the same way you challengesvar
s. If there isn’t a good justification for a particularwhile
ordo-while
loop, try to find a way to do the same thing without it. - For expression
usingfor
expression may make things clearer`persons withFilter (p => !p.isMale) flatMap (p =>
//clear(p.children map (c => (p.name, c.name) ) )
for (p <- persons; if !p.isMale; c <- p.children)yield (p.name, c.name)
</pre> in fact, every
forexpression can be expressed in terms of the three higher-order functions
map,
flatMapand
withFilter. So if you define
map,
flatMap,
withFilterand
foreachmethods for your data type, your data types could support
forexpression. If you just define a subset of these methods, then your data type would support a subset of all possible
for` expressions. live without
break
andcontinue
, tail-call.1. If the solution is tail recursive, there won’t be any runtime overhead to be paid.(_tail-call optimization_)
- tail-call optimization is limited to situations in which a method or nested function calls itself directly as its last operation.
Function and Closures
- programs should be decomposed into many small functions that each do a well-defined task.
- first-class functions
Every function value is an instance of some class that extends one of severalFuntionN
traits in packagesscala
. EachFunctionN
trait has anapply
method used to invoke the function. - partially applied functions
someNumbers.forach(sum _)
you could leave off the-
here (only when a function type is expected). - closures
when a closure accesses some variable that has several different copies as the program runs, the instance used is the one that was active at the time the closure was created.
Control Abstraction
Curring
By-name parameters
<pre>`def byNameAssert(predicate: => Boolean) =
if (assertionsEnabled && !predicate)
throw new AssertionError
def boolAssert(predicate: Boolean) =
if(assertionEnabled && !predicate)
throw new AsserstionError
//evaluate (5>3) before the call to boolAssert
boolAssert(5 > 3)
//the expression (5>3) is not evaluated before the call to byNameAssert.
byNameAssert(5 > 3)
`</pre>
Traits
A trait differs with a class:
1. no parameters
super
calls are dynamically bound
- if a trait delcares a superclass, then it can only be mixed into a class that also extends that superclass.
`trait Doubling extends IntQueue {
}abstract override def put(x: Int) { super.put(2*x) }
</pre>
supercall is dynamically bound, it will work so long as the trait is mixed in _after_ another trait or class that gives a concrete definition to the method.
abstract override` (only used for traits) means that the trait must be mixed into some class that has a concrete definition ot the method. - traits as stackable modifications
val queue = (new BasicIntQueue with Filtering with Incrementing)
traits further to the right take effect first. when determine thesuper
call, use linearization!4. If it might be reused in multiple, unrelated classes, use trait;
if you want to inherit from it in Java code, use abstract class;
if you plan to distribute it in compiled form, lean towards an abstract class;
if efficiency is very important, lean towards using a class.
Imports
flexible:
* may appear anywhere
- may refer to objects(singleton or regular) in addition to packages
- let you rename and hide some of the imported members
`import Fruits.{Apple => McIntosh} //rename apple //import all Fruits except for Apple import Fruits.{Apple => _, _} `
- scope of protection
private[X]
orprotected[X]
means that access is private or protected “up to” X, where X disignates some enclosing package, class or singleton object. - package objects
Each package is allowed to have one package object.
Assertions and Unit Testing
to be read
Case Classese && Pattern Matching && Extractors
case classes
1. _case class_ addes a factory method with the name of class.2. all arguments in the parameter list of a case class implicitly get a `val` prefix, so they are maintained as fields.
- the compiler addes methods
toString
,hashCode
,equals
to case class. - the compiler adds a
copy
method for making modified copies.
- the compiler addes methods
- a variable pattern matches any object, Scala binds the variable to whatever the object is.
To treat a lowercase identifier as a constant in a pattern match, use back-tickpi
.
- typed patterns
`def generalSize(x: Any) = x match { case s: String => s.length case m: Map[_, _] => m.size case _ => -1 } `
because of _type erasure_, you could not chek `case m: Map[Int, Int]`. Only exception to the erasure rule is arrays. `case a: Array[String]` . The element type of an array is stored with the array value, so you can pattern match on it. variable binding:`expr match { case Unop("abs", e @ Unop("abs", _) ) => e case _ => } `
- A sealed class cannot have any new subclasses added except the ones in the same file. If we want Scala compiler help to detect missing patterns, we should make the superclass of case classes sealed.
case sequences as partial functions6. patterns in
for
expressions
for (Some(fruit) <- results) println(fruit)
Extractors
A extractor is an object that has a method called
unapply
orunapplySeq
.`object Domain { def apply(parts: String*): String = parts.reverse.mkString(".") def unapplySeq(whole: String): Option[Seq[String]] = Some(whole.split("\\.").reverse) } dom match { case Domain("org", "acm") => println("acm.org") case Domain("net", _*) => println("a .net domain") `
- Compared with case classes, extractors is representation independence, that’s to say, patterns have nothing to do with the data type of the object that’s selected on. While case classes also have their advantages: short code and more efficient.
Collections
List & Sets and Maps
- type inference algorithm:
Type inference is flow based. In a method applicationm(args)
, it first checks whether the methodm
has a known type. If it has, that type is used to infer the expected type of the arguments.msort((x: Char, y: Char) => x > y)(abcdb) abcde sortWith (_ > _)
the type ofabcde
isList[Char]
, so we do not need to write it explicitly, could just use_
. - When designing a polymorphic method that takes some non-function arguments and a funtion argument, place the function argument last in a curried parameter list.( The methods’ correct instance type can be inferred from the non-function arguments).
scala.collection.mutable.Set()
andscala.collection.mutable.Map()
usually uses hash talbe. While forscala.collection.immutable.{Set(), Map()}
, it depends how many elements you pass to it. If there are more than 5 elements, then they use hash methods.
collection hierarchy
- the difference between
Traversable
andIterable
?
see discussion in stackOverflow - All collections except streams and views are strict. The only way to go from a strict to a lazy collection is via the
view
method. The only way to go back is viaforce
. a view is a special kind of collection that represents some base collection, but implements all of its transformers lazily.
1. Using view could avoiding intermediate results.
- create a subwindow for mutable sequences to update selectively some elements of that sequence.
`findRalindrome(words.view take 10000) //1 //2 val arr = (0 to 9).toArray val subarr = arr.view.slice(3, 6) do_something(subarr) // update partial of arr `
- create a subwindow for mutable sequences to update selectively some elements of that sequence.
- Factoring out common operations
CanBuildFrom
: check this post - integrate a new collection class into the framework
1. decide whether the collection should be mutable or immutable
- pick the right base traits for the collection
- inherit from the right implementation trait to implement most collection operations
- if you want
map
and similar operations to return instances of your collection type, provide an implicitCanBuildFrom
in your class’s companion object.
Type Parameterization
variance annotations
- covariant(
+
), contravariant(-
), nonvariant - In a purely functional word, many types are natureally covariant. However, the situation changes once you introduce mutable data. In fact, if a generic parameter type appears as the type of a method parameter, the containing class or trait may not be covariant in that type parameter.
- In scala, arrays are nonvariant. you could cast an array of
T
s to an array of any supertype ofT
`val a1 = Array("abc") val a2: Array[Object] = a1.asInstanceOf[Array[Object]] `
- lower bound
`class Queue[T](private val leading: List[T],
}private val trailing: List[T] ) { def enqueue[U >: T](x: U) = new Queue[U](leading, x :: trailing)//...
</pre> You coud append an
Orangeto a
Queue[Apple]. The result will be a
Queue[Fruit]`.s - Liskov Substitution Principle: It is safe to assume that a type
T
is a subtype of a typeU
if you can substitute a value of typeT
whereer a value of typeU
is required. The principle holds ifT
supports the same operations asU
and ll ofT
‘s operations require less and provides more than the corresponding operations inU
.
When you write the function typeA => B
, Scala expands this toFunction1[A,B]
.`trait Function[-S, +T] {
}def apply(x: S): T
</pre> The
Function1in the trait is contravariant in the function argument type
Sand covariant in the result type
T`, because argument are somthing that’s required, whereas results are something that’s provided. - upper bound
def orderedMergeSort[T <: Ordered[T]](xs: List[T]): List[T] = { ... }
Means the element type of the list passed toorderedMergeSort
must be a subtype ofOrdered
. - object private data
`class Queue[+T] private(
){ … }private[this] var leading: List[T], private[this] var trailing: List[T]
</pre> Here type
Tis a covariant type, but
leadingand
trailingare variable, there should be a contravariant position, there will be a compiler error. But if we mark
private[this]`, the compiler has special handling in it’s variance checking for this case.
Abstract Members
- one example
`trait Abstract { type T //abstract type def transform(x: T): T val initial: T var current: T } `
- For abstract vals, it could only be implemented by a
val
definition; For abstract method, it may be implemented by both concrete method definitions and concreteval
definitions. initializing abstract vals
`trait RationalTrait {
val numerArg: Int val denomArg: Int
}
//anonymous class
new RationalTrait {val numerArg = 1 val denomArg = 2 require(denomArg != 0)//exception ...
}
//pre-initialized
new {val numerArg = 1 * x val denomArg = 2 * x
} with RationalTrait
</pre> Here we first define a trait
RationalTraitwith abstract vals, then we have an _anonymous class_ that mixes in the trait. The anonymous class is initialized _after_ the
RationalTrait, so the values of
numerArgand
denomArgare not available during the initialization of
RationalTrait(i.e. theses two are 0). so the
requireinvocation will fail. so, a class parameter argument is evaluated _before_ it is passed to the class constructor(unless by-name). An implementing
val` definition in a subclass, by contrast, is evaluated only after the superclass has been initialized. Two solutions:* pre-initialized
- lazy vals
- path-dependent types
`class DogFood extends Food
class Dog extends Animal {
}type SuitableFood = DogFood override def eat(food: DogFood) {}
val bessy = new Dog
val lassie = new Dog</pre>
bessy.SuitableFoodis a _path-dependent type_ and it’s the same type as
lassie.SuitableFood. while <pre>
class Outer {
}class Inner
val o1 = new Outer
val o2 = new Outer</pre>
o1.Inneris a different type as
o2.Inner. They are subtypes of
OuterInner`. - structural subtyping
you get a subtyping relationship simply because two types have the same members.
class Pasture {* If you want to define a _Pasture_ class that contains animals that eat grass, you could write the type `Animal { type SuitableFood = Grass }`. so you could define _Pasture_ like this:
val animals: List[ Animal {type SuitableFood = Grass} ] = Nil }
- If you want to group together a number of classes that were written by someone else.
def using[T <: { def close(): Unit }, S](obj: T) (operation: T => S) = { val result = operation(obj) obj.close() result }
No base type is specified, so Scala would useAnyRef
automatically, and the typeT
must supportclose()
method.
- If you want to group together a number of classes that were written by someone else.
Implicit Conversions and Parameters
rules for implicits:
1. Marking Rule: Only definitions marked `implicit` are available.
- Scope Rule: An inserted implicit conversion must be in scope as a single identifier, or be associated with the source or target type of the conversion(i.e. companion object).
- One-at-a-time Rule: Only one implicit is tried.
- Explicits-First Rule: Whenever code type checks as it is written, no implicits are attempted.
Where implicits are tried:
1. implicit conversion to an expected type
`//1
implicit def int2double(x: Int): Double = x.toDouble
var i: Double = 1
//2 receiver
implicit def intToRational(x: Int) = new Rational(x, 1)
1 + oneHalf //intToRational(1) + oneHalf
//3 def maxListT(implicit orderer: T => Ordered[T]): T = elements match{ case List() => throw new IllegalArgumentException("empty list!") case List(x) => x case x :: rest => val maxRest = maxList(rest) //(orderer) is implicit if (x > maxRest) x //orderer(x) is implicit else maxRest }
maxList(List(1,5,10,3)) //res: Int = 10
//view bound
def maxList2T <% Ordered[T] : T = { …. }
//upper bound
def maxList3[T <: Ordered[T] }(elements: List[T]): T = { … }2. Converting the receiver: interoperating with new types; simulating new syntax 3. implicit parameters
view bound: We could use view bound to shorten the method header.
The difference between view bound and upper boundsT <: Ordered[T]
is that upper bounds require thatT
is a subtype ofOrdered[T]
, however, for view bound, it only requires that T can be treated as anOrdered[T]
. So we could pass aList[Int]
tomaxList
andmaxList2
but no tomaxList3
.multiple conversions
choose a more sprcific one. One implicit conversion is more spercific than another if one of the following applies:1. The argument type of the former is a subtype of the latters’s
- Both conversions are methods, and the enclosing class of the former extends the enclosing class of the latter.
Annotations
- standard annotations:
@deprecated
@volatitle
@serializable
@SerialVersionUID
@transient
`@scala.reflect.BeanProperty@tailrec
@unchecked
@native`
Modular Programming Using Object
- self type
check this post in StackOverflow. - singleton type
A singleton type is extremely specific and holds only one object. check this post
Actors and Concurrency
not too much to record, the example showed in Programming in Scala is worth understanding.