Scala
Classes
In Scala, we do this by defining a class :
class Rational(x:Int, y:Int){
def numer = x;
def denom = y
}
This definition introduces two entitiles:
-
A new type, name Rational.
-
A constructor Rational to create elements of this type.
Scala keeps the names of types and values in different namespaces
So there’s no confict between the two definitions of Rational
Objects
We call the elements of a class type objects.
We create an object by prefixing an application of the constructor of the class with the operator new.
- Example:
new Rational(1,2)
Members of an Object
Objects of the class Rational have two members, numer and denom.
We select the members of an object with the infix operator’.’(like in Java).
- Example
val x = new Rational(1,2) //> x : week3.Rational = week3.Rational@2c641e9a
x.numer //> res0: Int = 1
x.denom //> res1: Int = 2
Methods
One can go further and also package functions operating on a data abstraction in the data abstraction itself.
Such functions are called methods.
- Example
Rational numbers now would have, in addition to the functions numer and denom, the functions add,sub,mul,div,equal,toString
class Rational(x:Int, y:Int){
def numer = x
def denom = y
def add(that:Rational) = new Rational(numer*that.denom + denom*that.numer,
denom*that.denom)
def neg:Rational = new Rational(-numer,denom)
def sub(that:Rational) = add(that.neg)
override def toString = numer + "/" + denom
}
2-6: More Fun With Rationals
###Rationals with Data Abstraction
class Rational(x:Int, y:Int){
def numer = x/g
def denom = y/g
private def gcd(a:Int, b:Int) : Int = if (b == 0) a else gcd(b,a%b)
private val g = gcd(x,y)
...
}
定义私有成员g
和私有函数gcd
It is equally possible to turn number and denom into vals, so that they are computed only once:
class Rational(x:Int, y:Int){
val numer = x/gcd(x,y)
val denom = y/gcd(x,y)
...
}
这种优化和改动都在这个类的里面,并不影响调用者,这种对数据的封装成为data abstraction
Self Reference
On the inside of a class, the name this
represents the object on which the current method is executed.
- Example:
class Rational(x:Int, y:Int){
...
def less(that:Rational) = numer * that.denom < that.numer * denom
def max(that:Rational) = if(this.less(that)) that else this
...
}
Precondition
可以在类中加入对参数的合法性校验:
class Rational(x:Int, y:Int){
require(y != 0, "denominator can not be 0")
...
}
require
is a predefined function.
It takes a condition and an optional message string.
if the condition is false than IllegalArgumentException
is thrown with the given message string.
Assertions
Besides require
, there is also assert
Assert also takes a condition and an optional message:
val x = sqrt(y)
assert(x >= 0)
和require一样,assert在条件为false的时候也会抛出异常,但是异常类型为AssertionError
。
assert和require的目的不同:
-
require
is used to enforce a precondition on the caller of a function. -
assert
is used as to check the code of the function itself.
Constructor
默认情况下,Scala的类有默认的构造函数,如果想增加构造函数,需要将this
作为函数使用:
class Rational(x:Int, y:Int){
require(y != 0, "denominator can not be 0")
def this(x: Int) = this (x,1)
...
}
val z = new Rational(3)
2-7: Evaluations and Operations
Classes and Substitutions
我们之前可以使用substitution规则去分析函数,这节将这个规则用于class object
-
Question: How is an instantiation of the class
new C(e1,...,em)
-
Answer: The expression arguments
e1,...,em
参数和函数函数一样求值,得到:new C(v1,...,vm)
我们现在定义这样一个类:
class C(x1,...,xm){ ... def f(y1,...,yn) = b ...}
where:
-
类的构造函数入参为:
x1,...,xn
-
类中定义个了一个函数
f
,参数为y1,...,yn
-
Question: How is the following expression evaluated?
new C(v1,...,vm).f(w1,...,wn)
Operators
上面的例子中,求两个有理数的和,代码为x.add(y)
,这种没有x + y
直观,因此,scala提供一种方式使方法调用变的更直观:
它由两部分syntax构成:
- Step1: Infix Notation:
Any method with a parameter can be used like an infix operator.
it is therefore possible to write:
r add s => r.add(s)
r less s => r.less(s)
r max s => r.max(s)
- Step2: 定义操作符
Scala可以重载操作符:
def < (that:Rational) = numer * that.denom < that.numer * denom
调用变为:
x.less(y) 等价于 x < y
2-8: Class Hierarchies
Abstract Classes
Consider the task of writing a class for sets of integers with the following operations
定义一个可以表示整数集合的类
abstract class IntSet{
def incl(x:Int):InSet
def contains(x:Int):Boolean
}
抽象类:
IntSet is an abstract class.
抽象类可以定义接口,但不需要实现。 抽象类不可以直接用new创建
Class Extensions
Let’s consider implementing sets as binary trees.
There are two types of possible trees: a tree for the empty set, and a tree consisting of an integer and two sub-trees.
Here are their implementations
class Empty extends IntSet
{
def contains(x:Int):Boolean = false
def incl(x:Int):IntSet = new NonEmpty(x,new Empty, new Empty)
}
class NonEmpty(elem: Int, left: IntSet, right:IntSet) extends IntSet
{
def contains(x:Int):Boolean =
if (x<elem) left.contains(x)
else if (x>elem) right.contains(x)
else true
def incl(x:Int):IntSet =
if(x<elem) new NonEmpty(elem,left.incl(x), right)
else if(x>elem) new NonEmpty(elem, left, right.incl(x))
else this
}
###Base Classes and SubClasses
-
IntSet
is called the superclass of Empty and NonEmpty. -
Empty and NonEmpty are subclasses of
IntSet
. -
In Scala, any user-defined class extends another class.
-
If no superclass is given , the standard class object in the Java package
java.lang
is assumed -
The direct or indirect superclasses of a class C are called baseclasses of C
-
So, the base classes of NonEmpty are IntSet and Object
Implementing and Overriding
如果父类中的方法没有implementation,那么子类可直接实现,如果父类方法中有implementation,那么子类需要override:
abstract class Base
{
def foo = 1
def bar:Int
}
class Sub extends Base
{
override def foo = 2
def bar = 3
}
Object Definitions
上面的例子中,Empty类其实没有变化,但是每次都要重新创建,如果把它定义为一个单例,则能省去许多重复创建的instance,在Scala中,使用object
:
object class Empty extends IntSet
{
def contains(x:Int):Boolean = false
def incl(x:Int):IntSet = new NonEmpty(x,Empty,Empty)
override def toString = {"."}
}
-
This defines a singleton object named Empty.
-
No other Empty instances can be created.
-
Singleton objects are valeus , so Empty evaluates to itself
Programs
之前的代码都是使用REPL或workSheet运行,这一节将会创建一个Scala Object:
package week4
object week4 {
def main(args:Array[String]) = println("hello")
}
Dynamic Binding
Object-oriented languages(including Scala) implement dynmaic method dispatch.
This means that the code invoked by a method call depends on the runtime type of the object that contains the method.
- Example
Empty contains 1
-> [1/x][Empty/this] false
= false
Something to Ponder
Dynamic dispatch of methods is analogous to calls to higher-order functions.
- Question:
Can we implement one concept in terms of the other?
-
Objects in terms of higher-order functions?
-
HOF in terms of objects?
How class are organized
默认import的包:
-
All members of package scala
-
All members of package java.lang
-
All members of the singleton object scala.Predef.
Here are the fully qualified names of some types and functions which you have seen so far:
Int => scala.Int
Boolean => scala.Boolean
Obejct => java.lang.Object
require => scala.predef.require
assert => scala.predef.assert
Traits
In Java, as well as in Scala, a class can only have one superclass.
But what if a class has several natural supertypes to which it conforms or from which it wants to inherit code?
Here, you could use traits.
A trait is declared like an abstract class, just with trait
instead of abstract
class.
Trait类似Java中的interface
trait Planar
{
def height: Int
def width : Int
def surface = height*width
}
OBject只能继承一个class,但可以实现多个trait
class Square extends Shape with Plannar with Movable...
Traits resemble interfaces in Java, but are more powerful because they can contains fields and concrete methods.
On the other hand, traits cannot have parameters, only classes can
Trait中不能定义property
###Top Types
Scala有几个基本类型:
-
Any : the base type of all types, Method: ‘==’, ‘!=’, ‘equals’, ‘hashCode’, ‘toString’
-
AnyRef : The base type of all reference types; Alias of ‘java.lang.Object’
-
AnyVal : The base type of all primitive types.
###The Nothing Type
Nothing is at the bottom of Scala’s type hierarchy. It is a subtype of every other type.
There is no value of type Nothing.
Why is that useful?
-
To signal abnormal termination
-
As an element type of empty collections
###Exceptions
Scala’s exception handling is similar to Java’s
The expression:
def error(msg:String) = throw new Error(msg)
###The Null Type
Every reference class type also has null as a value.
The type of null
is Null
.
Null
is a subtype of every class that inherits from Object
; it is incompatible with subtypes of AnyVal
val x = null //x:Null = null
val y:String = null // y:String = null
val z:Int = null //error: type mismatch
if (true) 1 else false //> res0: AnyVal = 1
if的第一个分支返回的类型为Int,第二个分支返回的类型为boolean,那么表达式的类型应该是他们共同的类型:AnyVal
Polymorphism
以构建一个Con-Lists
为例,Con-List
是一个immutable linked list
它可以通过两种方式构建:
-
Nil
: 构建一个empty列表 -
Cons
一个包含一个element的cell和其余的list部分
Cons-Lists in Scala
Con-List
可以通过如下数据结构标示:
trait IntList
{
}
class Cons(val head:Int, val tail:IntList) extends IntList
{
}
class Nil extends IntList
{
}
Cons
的构造函数中参数用val
声明了,这种情况相当于自动给成员变量赋值:
class Cons(_head:Int, _tail:IntList) extends IntList
{
val heal = _head;
val tail = _tail;
}
Type Parameters
仅仅定义int类型的list太单一了,希望将其扩展到double,string类型,这时候需要使用泛型:
trait List[T]
class Cons[T](val head:T, val tail:List[T]) extends List[T]
class Nil[T] extends List[T]
定义trait
:
trait List[T]
{
def isEmpty:Boolean
def head:T
def tail:List[T]
}
定义Cons
,Nil
class Cons[T](val head:T, val tail:List[T]) extends List[T]
{
def isEmpty:Boolean = false
}
class Nil[T] extends List[T]
{
def isEmpty:Boolean = true
def head:Nothing = throw new NoSuchElementException("Nil.head")
def tail:Nothing = throw new NoSuchElementException("Nil.head")
}
###Generic Functions
和类一样,函数也支持泛型参数[T], 例如下面这个函数创建一个泛型List
def singleton[T](elem:T) = new Cons[T](elem, new Nil[T])
singleton[Int](1)
singleton[Boolean](true)
###Type Inference
事实上,编译器可以对参数类型进行推断,[T]可以省略:
singleton(1)
singleton(true)
Types and Evaluation
类型并不影响Scala进行参数替换,根据Substitution规则,参数被代入后,在执行表达式求值前,所有的类型都被remove掉。
这一特性叫做:type erasure
使用这一特性的语言有: Java,Scala,Haskell,ML,OCaml.
有些语言在runtime中仍然带着类型:C++,C#,F#
Polymorphism
这次来自希腊,意思是多种形式,In programming, 它表示:
-
函数的参数可以是多种类型的
-
某一种类型可以有很多子类型
我们目前已经看到过这两个特性:
-
子类: 可以将一个父类型的指针指向一个子类型
-
泛型: 函数参数和类的参数可以声明成泛型
###excise
已知一个list: List[T],要求返回第n个元素:
def nth[T](n:Int,xs:List[T]):T =
if (n==0) xs.head
else nth(n-1,xs.tail)
2-11 Functions as Objects
这个section讲object和function之间的关系:
###Functions as Objects
在Scala中,函数被认为是object:
例如A => B
这种函数类型其实是scala.Function1[A,B]
的缩写,定义为:
package scala
trait Function1[A,B]{
def apply(x:A):B
}
所以,函数只是object的apply
方法,同样的,对于多个函数入参的trait
还有Function2
,Function3
,…
Expansion of Function Values
匿名函数如下:
(x:Int) => x*x
被展开如下:
{
class AnonFun extends Function1[Int,Int]
{
def apply(x:Int):Int = x*x
}
new AnonFun
}
或者更简单的,使用匿名类:
new Function1[Int,Int]
{
def apply(x:Int) = x*x
}
Expansion of Function Calls
这样的函数调用f(a,b)
会扩展为:
f.apply(a,b)
val f = (x:Int) => x*x
f(7)
被扩展为:
val f = new Function1[Int,Int]{ def apply(x:Int) = x*x }
f.apply(7)
所以:Functions are just Objects
Functions and Methods
下面的方法:
def f(x:Int):Boolean = ...
由上一小节可知f
本身并不是函数类型,而是一个对象的apply
方法。
但是如果f
被放到了等号右边,需要成为一个函数类型的时候,会自动转换:
(x:Int) => f(x)
上面的匿名函数再展开:
new Function1[Int,Boolean]
{
def apply(x:Int) = f(x)
}
Objects Everywhere
到目前,我们
Scala Doc
www.scala-lang.org/api/current