Clojure首先是FP, 可是因为基于JVM, 因此不得已须要作出一些妥协, 包含一些OO的编程方式
Scala首先是OO, Java语法过于冗余, 一种比较平庸的语言, Scala首先作的是简化, 以更为简洁的方式来编写OO, 主要利用‘type inference’能推断出来的, 你就不用写, 但若是仅仅这样, 不如用python
因此Scala象其名字同样, “可伸展的语言”, 它是个大的集市, 它积极吸纳其余语言的优秀的特征, 最重要的就是FP, 你可使用Scala来写OO, 但它推荐使用FP的方式来写Scala; 还包括Erlang里面的actor模型
因此Scala并不容易学, 由于比较繁杂html
我主要对Scala programing作个笔记, 下面这个哥们整理的更好前端
http://qiujj.com/static/Scala-Handbook.htmjava
http://docs.scala-lang.org/ , Scala官方文档python
http://www.scala-lang.org/api/current/#package, 参考手册react
http://twitter.github.io/effectivescala/index-cn.html, Effective Scalagit
scala> 1 + 2
res0: Int = 3
scala> res0 * 3
res1: Int = 9
scala> println("Hello, world!")
Hello, world!
scala中;经常能够省略, 但若是一行有多条语句, 则必须加上程序员
val s = "hello"; println(s)
若是一条语句, 要用多行写es6
x
+ y
这样会看成2个语句, 两种方法解决, github
(x
+ y) //加括号 面试x +
y +
z //把操做符写在前一行, 暗示这句没完
Rich wrappers, 为基本类型提供更多的操做
val, 不可变变量, 常量, 适用于FP
var, 可变变量, 适用于OO
scala> val msg = "Hello, world!" msg: java.lang.String = Hello, world! scala> msg = "Goodbye cruel world!" <console>:5: error: reassignment to val msg = "Goodbye cruel world!"
能够简写为, 返回值类型不须要写, 能够推断出, 只有一条语句, 因此{}能够省略
scala> def max2(x: Int, y: Int) = if (x > y) x else y max2: (Int,Int)Int
简单的funciton, 返回值为Unit, 相似Void(区别在于void为无返回值, 而scala都有返回值, 只是返回的为Unit, ())
scala> def greet() = println("Hello, world!")
greet: ()Unit
scala> greet() == ()
Boolean = true
函数参数不可变
def add(b: Byte): Unit = {
b = 1 // This won’t compile, because b is a val
sum += b
}
重复参数, 最后的*表示可变参数列表, 可传入多个string
scala> def echo(args: String*) = for (arg <- args) println(arg)
scala> echo("hello", "world!")
hello
world!
如何翻译...
Scala FP的基础, function做为first class, 以function literal的形式做为参数被传递
args.foreach((arg: String) => println(arg))
args.foreach(arg => println(arg)) //省略类型
args.foreach(println) //其实连参赛列表也能够省略
能够看到scala在省略代码量上能够说下足功夫, 只要能推断出来的你均可以不写, 这也是对于静态类型系统的一种形式的弥补
对于oo程序员, 可能比较难理解, 其实等于
for (arg <args)
println(arg)
因为scala是偏向于FP的, 因此全部控制结构都有返回值, 这样便于FP编程
If, 能够返回值
val filename = if (!args.isEmpty) args(0) else "default.txt"
While, 在FP里面不推荐使用循环, 应该用递归,尽可能避免
For, 没有python和clojure的好用或简洁
for ( file <- filesHere //generator,用于遍历,每次file都会被重新初始化 if file.isFile; //过滤条件, 多个间须要用; if file.getName.endsWith(".scala"); //第二个过滤 line <- fileLines(file) //嵌套for trimmed = line.trim //Mid-stream variable bindings, val类型,相似clojure let if trimmed.matches(pattern) ) println(file +": "+ trimmed)
//for默认不会产生新的集合, 必须使用yield def scalaFiles = for { file <- filesHere if file.getName.endsWith(".scala") } yield file //yield产生新的集合,相似python
match, switch-case
能够返回值, FP风格, 这样只须要最后println一次
默认会break, 不须要每次本身加
val firstArg = if (!args.isEmpty) args(0) else "" val friend = firstArg match { case "salt" => "pepper" case "chips" => "salsa" case "eggs" => "bacon" case _ => "huh?" //default } println(friend)
参考, http://www.ibm.com/developerworks/cn/java/j-ft13/index.html
Option和Either都是用来让返回值能够有两个选择
而Option是比较简单的版本, 两个选择, 必定是成功Some, 和失败None
Option意味着可能有值some(x), 也可能没有值(用None对象, 表示缺失), 典型的例子就是从字典里取值
val capitals = Map("France" -> "Paris", "Japan" -> "Tokyo") def show(x: Option[String]) = x match { //Option类型, 可选的String case Some(s) => s case None => "?" } scala> show(capitals get "France") res24: String = Paris scala> show(capitals get "North Pole") res25: String = ?
之前的方式, 好比Java, 经过null来表示没有取到值, 可是有的时候null可能做为合法值出现, 就须要特殊处理, 很麻烦
而Scala提供option来比较优雅的解决这个问题
Either, 更为通用一些, 可用本身定义两种选择, 直接看个spark源码中的例子,
对于PutResult中的data, 有多是ByteBuffer或者Iterator
而使用的时候, 使用Left和Right来选择到底用哪个
private[spark] case class PutResult(size: Long, data: Either[Iterator[_], ByteBuffer])
PutResult(sizeEstimate, Left(values.iterator)) PutResult(bytes.limit(), Right(bytes.duplicate()))
这里不管option或either都提升了极好的灵活性, 在Java中若是要返回一个有两种可能性的值就比较不那么优雅了, 参考上面的连接
Scala支持这种隐式转换,
implicit def intToString(x: Int) = x.toString //定义转换函数后, 编译器会自动作转换
好比x op y没法经过类型检查, int x 不支持op操做, 而string支持, 编译器就会自动调用上面的转换函数, 将int x转化为string x
隐式参数
用法也比较诡异, 看例子
object Greeter { def greet(name: String)(implicit prompt: PreferredPrompt) {//声明2个参数, 第二个是能够隐式的,固然你也能够显式的写 println("Welcome, "+ name +". The system is ready.") println(prompt.preference) } } object JoesPrefs { implicit val prompt = new PreferredPrompt("Yes, master> ") //声明成implicit, 可用做补充 } import JoesPrefs._ scala> Greeter.greet("Joe") //编译器会自动补充成, greet("Joe")(prompt) Welcome, Joe. The system is ready. Yes, master>
可变的同类对象序列, 适用于OO场景
val greetStrings = new Array[String](3) //greetStrings为val, 可是内部的数组值是可变的
greetStrings(0) = "Hello" //scala用()而非[]
greetStrings(1) = ", "
greetStrings(2) = "world!\n"
for (i <- 0 to 2)
print(greetStrings(i))
Scala 操做符等价于方法, 因此任意方法均可以以操做符的形式使用
1 + 2 //(1).+(2), 在只有一个参数的状况下, 能够省略.和()
0 to 2 //(0).to(2)
greetStrings(0) //greetStrings.apply(0),这也是为何scala使用(), 而非[]
greetStrings(0) = "Hello" //greetStrings.update(0, "Hello")
简化的array初始化
val numNames = Array("zero", "one", "two") //Array.apply("zero", "one", "two")
相对于array, List为不可变对象序列, 适用于FP场景
val oneTwo = List(1, 2)
val threeFour = List(3, 4)val zeroOneTwo = 0 :: oneTwo //::
val oneTwoThreeFour = oneTwo ::: threeFour
对于List最经常使用的操做符为::, cons, 把新的elem放到list最前端
:::, 两个list的合并
右操做数, ::
普通状况下, 都是左操做数, 好比, a * b => a.*(b)
可是当方法名为:结尾时, 为右操做数
1 :: twoThree => twoThree.::(1)
不支持append
缘由是, 这个操做的耗时会随着list的长度变长而线性增加, 因此不支持, 只支持前端cons, 实在须要append能够考虑ListBuffer
支持模式
scala> val List(a, b, c) = fruit a: String = apples b: String = oranges c: String = pears scala> val a :: b :: rest = fruit a: String = apples b: String = oranges rest: List[String] = List(pears)
list zip,啮合
scala> abcde.indices zip abcde //indices返回全部有效索引值 res14: List[(Int, Char)] = List((0,a), (1,b), (2,c), (3,d),(4,e)) scala> val zipped = abcde zip List(1, 2, 3) zipped: List[(Char, Int)] = List((a,1), (b,2), (c,3)) //会自动截断
Folding lists: /: and :\
折叠操做, 比较奇怪的操做, 看例子
(z /: List(a, b, c)) (op) equals op(op(op(z, a), b), c) scala> def sum(xs: List[Int]): Int = (0 /: xs) (_ + _) //实现列表元素叠加 (List(a, b, c) :\ z) (op) equals op(a, op(b, op(c, z))) def flattenLeft[T](xss: List[List[T]]) = (List[T]() /: xss) (_ ::: _) //两种的区别 def flattenRight[T](xss: List[List[T]]) = (xss :\ List[T]()) (_ ::: _)
import scala.collection.immutable.Queue //不可变Queue
val empty = new Queue[Int] val has1 = empty.enqueue(1) //添加单个元素 val has123 = has1.enqueue(List(2, 3)) //添加多个元素 val (element, has23) = has123.dequeue //取出头元素,返回两个值, 头元素和剩下的queue element: Int = 1 has23: scala.collection.immutable.Queue[Int] = Queue(2,3)
import scala.collection.mutable.Queue //可变Queue val queue = new Queue[String] queue += "a" //添加单个 queue ++= List("b", "c") //添加多个 queue.dequeue //取出头元素, 只返回一个值 res22: String = a scala> queue res23: scala.collection.mutable.Queue[String] = Queue(b, c)
import scala.collection.mutable.Stack val stack = new Stack[Int] stack.push(1) stack.push(2) scala> stack.top res8: Int = 2 scala> stack.pop res10: Int = 2 scala> stack res11: scala.collection.mutable.Stack[Int] = Stack(1)
tuple和list同样是不可变的, 不一样是, list中的elem必须是同一种类型, 但tuple中能够包含不一样类型的elem
val pair = (99, "Luftballons") //自动推断出类型为,Tuple2[Int, String] println(pair._1) //从1开始,而不是0,依照Haskell and ML的传统 println(pair._2) //elem访问方式不一样于list, 因为元组中elem类型不一样
var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"
println(jetSet.contains("Cessna"))
val treasureMap = Map[Int, String]() treasureMap += (1 -> "Go to island.") treasureMap += (2 -> "Find big X on ground.") println(treasureMap(2))
val romanNumeral = Map(1 -> "I", 2 -> "II", 3 -> "III" ) //简写
Scala须要兼顾OO和FP, 因此须要提供mutable和immutable版本
这里默认是Immutable, 若是须要使用mutable版本, 须要在使用前显示的引用...
import scala.collection.mutable.Set
import scala.collection.mutable.Map
Sorted Set and Map
和Java同样, Scala也提供基于红黑树实现的有序的Set和Map
import scala.collection.immutable.TreeSet scala> val ts = TreeSet(9, 3, 1, 8, 0, 2, 7, 4, 6, 5) ts: scala.collection.immutable.SortedSet[Int] = Set(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) scala> val cs = TreeSet('f', 'u', 'n') cs: scala.collection.immutable.SortedSet[Char] = Set(f, n, u) import scala.collection.immutable.TreeMap scala> var tm = TreeMap(3 ->'x', 1 ->'x', 4 ->'x') scala> tm += (2 ->'x') scala> tm res38: scala.collection.immutable.SortedMap[Int,Char] = Map(1 ->x, 2 ->x, 3 ->x, 4 ->x)
相对于Java定义比较简单, 默认public
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = {
sum += b
}def checksum(): Int = {
return ~(sum & 0xFF) + 1
}
}
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = sum += b
def checksum(): Int = ~(sum & 0xFF) + 1
}
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte) { sum += b } //对于Unit返回的, 另外一种简写, 用{}来表示无返回, 因此前面的就不用写了
def checksum(): Int = ~(sum & 0xFF) + 1
}
实例化
val acc = new ChecksumAccumulator
val csa = new ChecksumAccumulator
acc.sum = 3
Singleton对象
Scala不能定义静态成员, 因此用Singleton对象来达到一样的目的
import scala.collection.mutable.Map object ChecksumAccumulator { //用object代替class private val cache = Map[String, Int]() def calculate(s: String): Int = if (cache.contains(s)) cache(s) else { val acc = new ChecksumAccumulator for (c <s) acc.add(c.toByte) val cs = acc.checksum() cache += (s -> cs) cs } }
最多见的场景就是, 做为scala程序的入口,
To run a Scala program, you must supply the name of a standalone singleton object with a main method that takes one parameter(Array[String]), and has a result type of Unit.
import ChecksumAccumulator.calculate object Summer { def main(args: Array[String]) { for (arg <args) println(arg +": "+ calculate(arg)) } }
不可变对象
适用于FP场景的对象, 因此也叫作Functional Objects.
好处, 消除可变带来的复杂性, 能够放心的当参数传递, 多线程下使用啊...
下面以定义有理数类为例
class Rational(n: Int, d: Int) //极简方式,没有类主体
和上面的定义比, 不需定义成员变量, 而只是经过参数, 由于根本没有定义成员变量, 因此无处可变.
class Rational(n: Int, d: Int) { require(d != 0) //Precondition, 若是require返回false会抛出IllegalArgumentException,阻止初始化 private val g = gcd(n.abs, d.abs) val numer = n / g //添加不可变成员字段,便于引用 val denom = d / g def this(n: Int) = this(n, 1) //辅助构造函数 def + (that: Rational): Rational = //定义操做符 new Rational( numer * that.denom + that.numer * denom, denom * that.denom //也可使用this.number引用成员 ) def + (i: Int): Rational = //典型的成员函数重载 new Rational(numer + i * denom, denom) override def toString = numer +"/"+ denom //override, 方法重载 private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) }
可变对象
可变Objects自己没啥好说的, 说说Scala getter和setter规则
every var that is a non-private member of some object implicitly defines a getter and a setter method with it.
The getter of a var x is just named “x”, while its setter is named “x_=”.
每一个非私有的var都会隐含的自动定义getter和setter, 以下面的例子
class Time { var hour = 12 var minute = 0 } //等同于 class Time { private[this] var h = 12 private[this] var m = 0 def hour: Int = h def hour_=(x: Int) { h = x } def minute: Int = m def minute_=(x: Int) { m = x } }
因此在Scala中比较有趣的是, 其实你能够不真正定义这个成员, 而只须要定义getter和setter就能够
以下面的例子, 并无真正定义华氏温度, 而只是定义了getter和setter, 更简洁
class Thermometer { var celsius: Float = _ def fahrenheit = celsius * 9 / 5 + 32 def fahrenheit_= (f: Float) { celsius = (f 32) * 5 / 9 } override def toString = fahrenheit +"F/"+ celsius +"C" }
抽象类
abstract class Element { //abstract表示抽象类 def contents: Array[String] //没有函数体表示声明抽象方法 }
无参函数和属性, 统一访问模式
客户能够用一样的方式来访问函数width或属性width, 这样的好处是width改变不一样的实现方式时, 不会影响客户代码, 实现统一访问模式
函数和属性的区别, 函数节省空间而须要每次计算, 属性是初始化时算好的, 比较快, 但须要额外的空间, 因此可用在不一样场景下选择不一样实现
abstract class Element { def contents: Array[String] //组合Array[] def height: Int = contents.length //无参函数 def width: Int = if (height == 0) 0 else contents(0).length } //等同于 abstract class Element { def contents: Array[String] val height = contents.length //属性 val width = if (height == 0) 0 else contents(0).length }
Scala为了实现这种统一的访问模式, 通常对于无参函数在调用时能够省掉(), 但前提是该无参函数是没有反作用的, 不然就会比较confusing
"hello".length // no () because no sideeffect println() // better to not drop the (),由于有反作用
继承
超类的私有成员不会被子类继承.
抽象成员, 须要被子类实现(implement)
通常成员, 可用被子类重写(override)
override关键字的使用,
重写时, 必须显式加override, 防止意外的重写
实现时, 是否写override可选
其余状况禁止使用
class ArrayElement(conts: Array[String]) extends Element { def contents: Array[String] = conts //实现抽象函数 }
这个图, 反映出继承和组合, 其中Element继承自AnyRef, 而ArrayElement组合Array[]
重写方法和属性
这是scala比较特别的地方, 方法和属性在同一个命名空间, 因此方法和属性不能同名
而且支持, 在override时, 方法和属性间可用互相转换
前面将contents声明成方法, 是否以为有些别扭, 看上去更像属性, 是的, 你可用在子类中实现或重写成属性
在scala中, 无反作用的无参函数和属性是能够简单的互相转换
class ArrayElement(conts: Array[String]) extends Element { val contents: Array[String] = conts //将方法contents实现成属性 }
Java和Scala的命名空间对比
Java’s four namespaces are fields, methods, types, and packages.
By contrast, Scala’s two namespaces are:
• values (fields, methods, packages, and singleton objects)
• types (class and trait names)
参数化属性
一种scala的简化,
是否以为, 定义参数, 再将参数赋值给属性, 很麻烦, ok, scala可用简化
class ArrayElement( val contents: Array[String] ) extends Element //实现属性
另外一个例子,
class Cat { val dangerous = false } class Tiger( override val dangerous: Boolean, //重写属性 private var age: Int //增长新的属性 ) extends Cat
超类构造器
和C++或Pythno同样, 须要在子类中显式的构造超类, 好比这里须要先构造ArrayElement
只不过Scala提供一种简写的方式, 而不用在代码中另写一行
class LineElement(s: String) extends ArrayElement(Array(s)) { override def width = s.length override def height = 1 }
final成员
class ArrayElement extends Element { final override def demo() { //该成员函数禁止被任何子类override println("ArrayElement's implementation invoked") } } final class ArrayElement extends Element {//ArrayElement禁止被任何子类继承 override def demo() { println("ArrayElement's implementation invoked") } }
工厂对象实现
object Element { //用伴生对象(Singleton)来作工厂对象 def elem(contents: Array[String]): Element = new ArrayElement(contents) def elem(chr: Char, width: Int, height: Int): Element = new UniformElement(chr, width, height) def elem(line: String): Element = new LineElement(line) }
At the top of the hierarchy is class Any.
Any声明以下接口, 故全部类都支持这些接口,
final def ==(that: Any): Boolean final def !=(that: Any): Boolean def equals(that: Any): Boolean def hashCode: Int def toString: String
Any有两个子类, AnyVal和AnyRef
AnyVal是全部built-in value class的父类, 除了常见的基本类型外, 还有Unit
AnyRef是全部reference classes的父类, 其实就是java.lang.Object
Scala还定义, Nothing和Null class
Nothing是全部Any的子类, 而Null是全部AnyRef的子类
干嘛用的?用于以统一的方式来处理边界形式或异常返回, 由于他们是全部类的子类, 全部可用返回给任意返回类型
例子,
def error(message: String): Nothing = throw new RuntimeException(message) def divide(x: Int, y: Int): Int = //返回类型为Int if (y != 0) x / y else error("can't divide by zero") //异常时返回Nothing
支持package嵌套和经过{}指定范围
package launch { class Booster3 } package bobsrockets { package navigation { class Navigator } package launch { class Booster { // No need to say bobsrockets.navigation.Navigator val nav = new navigation.Navigator val booster3 = new _root_.launch.Booster3 //_root_特指顶层包 } } }
Import的用法
import bobsdelights.Fruit import bobsdelights._ //import all import Fruits.{Apple, Orange} //指定import import Fruits.{Apple => McIntosh, Orange} //别名 import Fruits.{Apple => _, _} //排除Apple
Private
基本和Java一致,可是在对inner class上表现出更好的一致性
Java容许外部类访问内部类的private成员,而scala不行,以下例子中的error case
class Outer { class Inner { private def f() { println("f") } class InnerMost { f() // OK } } (new Inner).f() // error: f is not accessible }
Protected
一样比Java更严格一些,仅容许在子类中被访问
而Java容许同一个包中的其余类访问该类的protected成员,而scala中不行,以下面的error case
package p { class Super { protected def f() { println("f") } } class Sub extends Super { f() } class Other { (new Super).f() // error: f is not accessible } }
修饰符做用域
scala中的修饰符能够加上做用域,很是灵活
private[X] or protected[X] means that access is private or protected “up to” X, where X designates some enclosing package, class or singleton object.
加上做用域的意思,除了原来private和protect表示的可见范围外,将可见范围扩展到X,X能够是package,class,singleton对象
package bobsrockets { package navigation { private[bobsrockets] class Navigator { //使Navigator类在bobsrockets package中可见,因此在package launch中能够new Navigator protected[navigation] def useStarChart() {} //在Navigator和子类,以及package Navigation中能够被访问(等同于Java) class LegOfJourney { private[Navigator] val distance = 100 //是私有变量distance,在外部类Navigtor中可见(等同于Java) } private[this] var speed = 200 //仅仅在相同对象中能够访问,比private的相同类访问还严格,以下例 val other = new Navigator other.speed // this line would not compile } } package launch { import navigation._ object Vehicle { private[launch] val guide = new Navigator } } }
伴生对象
A class shares all its access rights with its companion object and vice versa.
概念上相似Java的interface, 可是更强大, 能够有方法实现, 属性...
其实几乎等同于class, 除了两点,
a. trait不能有任何参数
b. trait中super是动态绑定的, 由于定义的时候根本就不知道super是谁
trait Philosophical {
def philosophize() {
println("I consume memory, therefore I am!")
}
}
mix in有两种方式, extend和with
extend, implicitly inherit the trait’s superclass
因此下面的例子, Frog会继承Philosophical的超类AnyRef
class Frog extends Philosophical { //mix in Philosophical trait override def toString = "green" }
with, 须要显式的指明超类
class Animal trait HasLegs class Frog extends Animal with Philosophical with HasLegs {//继承Animal,并mix in两个traits override def toString = "green" }
Trait真正的做用在于, 模块封装, 能够把相对独立的功能封装在trait中, 并在须要的时候进行mix in, 以下面的例子
//传统的例子 class Rational(n: Int, d: Int) { // ... def < (that: Rational) = this.numer * that.denom > that.numer * this.denom def > (that: Rational) = that < this def <= (that: Rational) = (this < that) || (this == that) def >= (that: Rational) = (this > that) || (this == that) } //将比较接口和实现封装在Ordered Trait中,这里仅仅须要mix in和实现compare class Rational(n: Int, d: Int) extends Ordered[Rational] { // ... def compare(that: Rational) = (this.numer * that.denom) ( that.numer * this.denom) }
Traits as stackable modifications
trait的强大还体如今能够同时mix in多个traits, 并以相似stackable形式的线性调用, 即多个trait会效果叠加
而传统的多重继承, 只有一个实现会有效
abstract class IntQueue { def get(): Int def put(x: Int) } import scala.collection.mutable.ArrayBuffer class BasicIntQueue extends IntQueue { private val buf = new ArrayBuffer[Int] def get() = buf.remove(0) def put(x: Int) { buf += x } } trait Doubling extends IntQueue { //trait继承自IntQueue, 说明该trait只能mix in IntQueue(或子类)中 //注意这里super的使用(动态绑定), 和abstract override(仅用于这种场景) //表示trait must be mixed into some class that has a concrete definition of the method in question abstract override def put(x: Int) { super.put(2 * x) } } trait Incrementing extends IntQueue { abstract override def put(x: Int) { super.put(x + 1) } } trait Filtering extends IntQueue { abstract override def put(x: Int) { if (x >= 0) super.put(x) } }
下面来看看, 怎么样进行stackable的调用
//同时mix in了Incrementing和Filtering //注意调用顺序是, 从右到左(stackable) //这个例子, 先调用Filtering, 再调用Incrementing, 顺序不一样会带来不一样的结果 scala> val queue = (new BasicIntQueue with Incrementing with Filtering) queue: BasicIntQueue with Incrementing with Filtering... scala> queue.put(-1); queue.put(0); queue.put(1) scala> queue.get() res15: Int = 1 scala> queue.get() res16: Int = 2
To trait, or not to trait
If the behavior will not be reused, then make it a concrete class.
If it might be reused in multiple, unrelated classes, make it a trait.
If you want to inherit from it in Java code, use an abstract class.
If you plan to distribute it in compiled form, and you expect outside groups to write classes inheriting from it, you might lean towards using an abstract class.
If efficiency is very important, lean towards using a class.
成员函数, OO的方式
内部函数, 须要切分功能, 又不想污染外部的命名空间
First-class function, unnamed function literal
function象变量同样, 能够被赋值和当参数传递, 但在scala须要以function literal的形式, 在运行期的时候会实例化为函数值(function value)
scala> var increase = (x: Int) => x + 1 scala> increase(10)
Partially applied functions
scala> def sum(a: Int, b: Int, c: Int) = a + b + c scala> val a = sum _ //用占位符代替整个参数列表 scala> a(1, 2, 3) //a.apply(1, 2, 3) res13: Int = 6 scala> val b = sum(1, _: Int, 3) //partial function, 用占位符代替一个参数 scala> b(2) res15: Int = 6
Closures
关于闭包的解释,
对于一般的function, (x: Int) => x + 1, 称为closed term
而对于(x: Int) => x + more, 称为open term
因此对于开放的, 必须在定义的时候对里面的自由变量more动态进行绑定, 因此上下文中必需要有对more的定义, 这种关闭open term过程产生了closure
scala> var more = 1 scala> val addMore = (x: Int) => x + more //产生闭包,绑定more scala> addMore(10) res19: Int = 11 scala> more = 9999 scala> addMore(10) res21: Int = 10009 //可见闭包绑定的不是value,而是变量自己
刚看到有些惊讶, 去clojure里面试一下, 也是这样的, 绑定的变量自己, 闭包会取最新的值
固然通常不会这样使用闭包.
下面这个例子, 是较经常使用的case, 其中闭合了函数的参数
如何在闭包调用时, 能够访问到已经不存在的变量? 当产生闭包时, 编译器会将这个变量从堆栈放到堆里面, 因此函数结束后还能访问
def makeIncreaser(more: Int) = (x: Int) => x + more
scala> val inc1 = makeIncreaser(1)
scala> val inc9999 = makeIncreaser(9999)
scala> inc1(10)
res24: Int = 11
scala> inc9999(10)
res25: Int = 10009
先提供sum的两个版本的比较,
scala> def plainOldSum(x: Int, y: Int) = x + y
scala> plainOldSum(1, 2)
res4: Int = 3
scala> def curriedSum(x: Int)(y: Int) = x + y //currying版本的sum
curriedSum: (Int)(Int)Int
scala> curriedSum(1)(2)
res5: Int = 3
其实currying, 等同于调用两次function, first会返回第二个函数的函数值, 其中closure了x
scala> def first(x: Int) = (y: Int) => x + y first: (Int)(Int) => Int
取出函数值, 效果是减小了参数个数, 第一个函数的参数已经closure在第二个函数中了, 和partially有些相似(区别)
scala> val onePlus = curriedSum(1)_ onePlus: (Int) => Int = <function> scala> onePlus(2) res7: Int = 3
有什么用? 用于建立更像built-in的控制结构
以下, 使用{}更像built-in, 但{}有个限制是, 只有单个参数的参数列表能够用{}替换(), 因此这个时候须要用currying来下降参赛个数
scala> println("Hello, world!") //象方法调用 scala> println { "Hello, world!" } //更像built-in的控制结构,好比if
对于FP, 相对于OO使用继承和多态, 使用函数做为参数来实现代码重用, 但愿能够将函数值放在{}, 显得更象built-in
好比下面, 每次打开文件, 操做, 关闭文件, 固定模式, 因此实现withPrintWriter, 每次传入不一样的op就能够进行不一样的操做, 而不用考虑文件开关
若是是oo实现, 就须要传入基类对象, 利用多态实现, 明显使用函数更轻量级一些
def withPrintWriter(file: File, op: PrintWriter => Unit) { val writer = new PrintWriter(file) try { op(writer) } finally { writer.close() } } //以调用方法的方式使用 withPrintWriter( new File("date.txt"), writer => writer.println(new java.util.Date) )
经过currying减小了参数, 因此就可使用{}
def withPrintWriter(file: File)(op: PrintWriter => Unit) {......} //currying版本 val file = new File("date.txt") withPrintWriter(file) { writer => writer.println(new java.util.Date) } //将函数值放在{}, 很像built-in
前面说了, FP尽可能避免使用循环, 而应该使用递归
可是递归效率有问题, 不停的压栈, 也很容易爆堆栈
因此对于某种递归, 尾递归, 编译器会自动优化成循环执行, 避免屡次使用堆栈
局限是, 不是什么状况都能写成尾递归, 其实只有循环能够...
比clojuer好, 编译器会自动进行优化
//while, 循环版本,oo def approximateLoop(initialGuess: Double): Double = { var guess = initialGuess while (!isGoodEnough(guess)) guess = improve(guess) guess } //递归版本,FP def approximate(guess: Double): Double = if (isGoodEnough(guess)) guess else approximate(improve(guess))
pattern matching (模式匹配是FP里面比较高级的特性), 能够参考一下clojure的Multimethods
那么Scala要如何来支持pattern matching?
Case Class
首先case class是用于让你更加简洁的使用pattern matching, 若是你想对一个class进行pattern matching, 最好在前面加上case, 以下面的例子(string和double的一元或二元操做)
abstract class Expr case class Var(name: String) extends Expr case class Number(num: Double) extends Expr case class UnOp(operator: String, arg: Expr) extends Expr case class BinOp(operator: String, left: Expr, right: Expr) extends Expr
那么成为case class, 如何就能便于pattern matching?
1. 添加与类名同样的工厂方法, 即建立类对象时不须要new, 更简洁
scala> val v = Var("x") //替代new Var("x")
scala> val op = BinOp("+", Number(1), v)//new BinOp("+", new Number(1), v)
2. all arguments in the parameter list of a case class implicitly get a val prefix, so they are maintained as fields. 参数会默认隐含的加上val前缀, 因此能够看成field来用
scala> v.name res0: String = x scala> op.left res1: Expr = Number(1.0)
3. the compiler adds “natural” implementations of methods toString, hashCode, and equals to your class. 意味着能够用于println和"=="
scala> println(op) BinOp(+,Number(1.0),Var(x)) scala> op.right == Var("x") res3: Boolean = true
case class带来的反作用就是你的类会比原来的大一些
Pattern matching
下面给个例子来讲明, 实现方式很是相似switch…case
match关键字代表匹配前面的expr
每一个case, case开头, 跟着须要匹配的模式, =>后面加上执行语句
下面的例子的意思, 简化expr, 当出现两个-号, +0, 或*1的状况下, 就直接返回变量e就能够
def simplifyTop(expr: Expr): Expr = expr match { case UnOp("-", UnOp("-", e)) => e //UnOp构造器匹配(匹配类型和参数), "-"常量匹配(使用==), e变量匹配(任意值,并方便后面引用) case BinOp("+", e, Number(0)) => e case BinOp("*", e, Number(1)) => e // Multiplying by one case _ => expr //-通配符匹配, 匹配任意值,但没法引用 }
和switch case的区别
1. match是scala中的expression, 因此必定会返回值
2. 不用加break, 匹配即返回
3. 若是没有匹配上, 会抛MatchError异常, 因此你必须考虑到全部case, 或加上默认case
Pattern种类
Wildcard patterns, 通配符模式
catch all, 用于忽略你不关心的部分
expr match { case BinOp(_, _, _) => println(expr +"is a binary operation") case _ => println("It's something else") }
Constant patterns, 常量模式
Any literal may be used as a constant. For example, 5, true, and "hello" are all constant patterns.
Also, any val or singleton object can be used as a constant.
def describe(x: Any) = x match { case 5 => "five" case true => "truth" case "hello" => "hi!" case Nil => "the empty list" //单例对象Nil匹配空列表 case _ => "something else" }
Variable patterns, 变量模式
变量模式, 和通配符同样是catch all, 可是区别就在于, 后面能够经过变量引用
expr match { case 0 => "zero" case somethingElse => "not zero: "+ somethingElse }
变量模式和常量模式的区别
scala> import Math.{E, Pi} import Math.{E, Pi} scala> E match { case Pi => "strange math? Pi = "+ Pi //Pi是常量而非变量 case _ => "OK" } res10: java.lang.String = OK全部小写字母开头的为变量模式, 其余的都是常量模式
若是要用小写字母开头表示变量, 两种方法, 加前缀this.pi, 加反引号`pi`
Constructor patterns, 构造器模式
这个模式使得scala的模式匹配显得格外强大, 由于这样能够deep match
好比下面的例子, 能够匹配BinOP类, 类参数+, e, Number类, Number类的参数0, 能够达到3层的deep match
expr match { case BinOp("+", e, Number(0)) => println("a deep match") case _ => }
Sequence patterns, 序列模式
expr match { case List(0, _, _) => println("found it") //第一个为0的length为3的List case _ => } expr match { case List(0, _*) => println("found it") //第一个为0,可变长的List case _ => }
Tuple patterns, 元组模式
特别之处在于, 能够用tuple给多个变量赋值
def tupleDemo(expr: Any) = expr match { case (a, b, c) => println("matched "+ a + b + c) case _ => }
Typed patterns, 类型模式
须要注意的是, 在类型模式下用s替换x
缘由在于, x的类型是Any, 因此你没法调用x.length, 因此必须使用string s来替换x
def generalSize(x: Any) = x match { case s: String => s.length case m: Map[_, _] => m.size //匹配任意Map case _ => -1 }
能够看到这里很方便, 若是你对比一下使用传统的isInstanceOf and asInstanceOf的方式
Type erasure, 类型擦除
能够看到运行时, 编译器是没法check Map中数据的类型的, 由于Scala和Java同样使用了erasure model of generics(泛型的擦除模式), 参数类型信息没有保留到运行期
scala> def isIntIntMap(x: Any) = x match { case m: Map[Int, Int] => true //会报unchecked warning, 由于没法check Map中是否为Int case _ => false } scala> isIntIntMap(Map(1 >1)) res17: Boolean = true scala> isIntIntMap(Map("abc" >"abc")) res18: Boolean = true //能够看出, 实际上是没法区分Map中数据类型的
可是, 数组是惟一的意外, 由于他们被特殊实现, 在运行期会保留参数类型信息,
scala> def isStringArray(x: Any) = x match { case a: Array[String] => "yes" case _ => "no" } scala> val ai = Array(1, 2, 3) scala> isStringArray(ai) res20: java.lang.String = no //对于数组能够识别出
Variable binding, 变量绑定
经过@将UnOp("abs", _)绑定到变量e上
expr match { case UnOp("abs", e @ UnOp("abs", _)) => e case _ => }
Pattern guards
其实就是在模式后面加上必定的条件判断, 经过了才会执行后面的语句
好比下面的例子, 判断x是否等于y,
scala> def simplifyAdd(e: Expr) = e match { case BinOp("+", x, y) if x == y => BinOp("*", x, Number(2)) //加上条件判断, 称为pattern guard case _ => e }
Pattern overlaps
Scala支持多个Pattern重叠, 好比下面的例子, 匹配第一个的, 必定也会匹配第二个, 第三个
但顺序不能反, 必须先具体的, 后general的, 不然编译器会报错
def simplifyAll(expr: Expr): Expr = expr match { case UnOp("-",UnOp("-",e)) => simplifyAll(e) // ‘-’is its own inverse case UnOp(op, e) => UnOp(op, simplifyAll(e)) case _ => expr }
Sealed classes
Sealed意味着只能在这个文件定义子类, 这样就不能随便在其余地方增长case类
这样作的缘由是, scala要求模式匹配时须要考虑到全部的状况, 因此程序员都须要知道到底有哪些case class
经过Sealed class, 不但程序员能够在该文件中找到全部的case class, 并且scala编译器也会找到, 并在编译时作检查,
sealed abstract class Expr //sealed case class Var(name: String) extends Expr case class Number(num: Double) extends Expr case class UnOp(operator: String, arg: Expr) extends Expr case class BinOp(operator: String, left: Expr, right: Expr) extends Expr
def describe(e: Expr): String = (e: @unchecked) match { //@unchecked告诉编译器不用check这个match的case case Number(_) => "a number" case Var(_) => "a variable" }
在处理并发上, Scala采起了和Clojure彻底不一样的思路, 采起actor模式尽可能避免并发冲突的可能性, 用于补充Java传统的并发模式
Actor模式就不解释了, 这里actor就是一个线程
下面定义的echoActor, 会不停的读inbox, 并打印出收到的message
val echoActor = actor { while (true) { receive { case msg => println("received message: "+ msg) } } }
而后, 如何给actor发消息? 很简单, 以下
scala> echoActor ! "hi there" received message: hi there //echoActor线程收到message, 并打印出来
而且这里receive只会接收匹配的message(即匹配receive中的case的), 对于不匹配的message不会去处理
在scala中也能够将native线程看成actor来用, 而且经过Actor.self来当前线程看成actor看
import scala.actors.Actor._ scala> self ! "hello" //给当前线程actor发送消息 scala> self.receive { case x => x } //当前线程receive res6: Any = hello
这里须要先发message, 再recieve, 由于receive是block函数, 没有message不会返回
固然也可使用receiveWithin, 来设置timeout
scala> self.receiveWithin(1000) { case x => x } // wait a sec! res7: Any = TIMEOUT
actor模式问题是, 每一个actor都须要一个线程, 因此若是须要定义大量的actor效率会比较低
JVM只能建立几千个线程, 而且线程间的切换也是比较低效的
因此使用react方法能够共享线程以提升效率...
原理在于, Scala actor模块会维护一个线程池, 用于分配线程给每一个线程
对于receive方法, 分配的线程会一直被actor占住, 由于receive会返回值须要保持当前的调用栈
而react方法, 不会返回, 不须要保持调用栈, 因此执行完这个线程就能够被释放, 用于其余的actor
object NameResolver extends Actor { import java.net.{InetAddress, UnknownHostException} def act() { react { case (name: String, actor: Actor) => actor ! getIp(name) act() case "EXIT" => println("Name resolver exiting.")// quit case msg => println("Unhandled message: "+ msg) act() } } def getIp(name: String): Option[InetAddress] = { try { Some(InetAddress.getByName(name)) } catch { case _:UnknownHostException => None } } }
How react works
A return type of Nothing indicates a function will never return normally, but instead will always complete abruptly with an exception. And indeed, this is true of react.
react的返回值是Nothing, 意思是不会正常返回, 即以异常做为react的结束
The actual implementation of react is not as simple as the following description, and subject to change, but conceptually you can think of react as working like this:
When you call start on an actor, the start method will in some way arrange things such that some thread will eventually call act on that actor. If that act method invokes react, the react method will look in the actor’s mailbox for a message the passed partial function can handle. (It does this the same way as receive, by passing candidate messages to the partial function’s isDefinedAt method.)
前面都是和receive同样的, 线程调用act, act调用react从actor的mailbox里面check是否有须要处理的message
If it finds a message that can be handled, react will schedule the handling of that message for later execution and throw an exception.
If it doesn’t find one, it will place the actor in “cold storage,” to be resurrected if and when it gets more messages in its mailbox, and throw an exception.
In either case, react will complete abruptly with this exception, and so will act. The thread that invoked act will catch the exception, forget about the actor, and move on to other duties.
若是找到须要处理的message, react就会处理这条message, 最终抛出exception, 若是找不到合适的message, 会将这个actor暂时休眠, 并抛出exception
能够看到, 这里的关键在于, react必定会以exception结束, 最终调用act的线程会catch到这个exception, 知道react已经完成, 因而接着处理其余的事情, 因此达到线程重用
This is why if you want react to handle more than the first message, you’ll need to call act again from inside your partial function, or use some other means to get react invoked again.
使用Actor.Loop来反复执行消息处理, 而不能用while
def act() { loop { react { case (name: String, actor: Actor) => actor ! getIp(name) case msg => println("Unhandled message: " + msg) } } }
主Actor不该被阻塞, 由于这样他就不能及时的响应其余消息
处理的方法, 就是阻塞逻辑放到一个新的actor里面执行, 由于这个actor不会接收其余消息, 因此被阻塞是没有影响的
val sillyActor2 = actor { def emoteLater() { val mainActor = self actor { //使用新的actor Thread.sleep(1000) //阻塞逻辑 mainActor ! "Emote" //将结果返回给主actor } } var emoted = 0 emoteLater() loop { react { case "Emote" => println("I'm acting!") emoted += 1 if (emoted < 5) emoteLater() //这里没有直接执行阻塞逻辑, 而是调用新的actor来执行 case msg => println("Received: "+ msg) } } }
实际运行结果, 可见发送给sillyActor2的消息当即会获得处理, 不会被阻塞
scala> sillyActor2 ! "hi there"
scala> Received: hi there
I'm acting!
I'm acting!
I'm acting!
经过case class来定义消息格式
不然若是只是简单的, lookerUpper ! ("www.scalalang.org", self), 其余人很难理解你的消息
import scala.actors.Actor._ import java.net.{InetAddress, UnknownHostException} case class LookupIP(name: String, respondTo: Actor) //经过case class来定义消息格式, 来便于理解 case class LookupResult( name: String, address: Option[InetAddress] ) object NameResolver2 extends Actor { def act() { loop { react { case LookupIP(name, actor) => actor ! LookupResult(name, getIp(name)) } } } def getIp(name: String): Option[InetAddress] = { // As before (in Listing 30.3) } }