scala 基础语法
文章内容全部来自:http://twitter.github.io/scala_school/zh_cn/index.html
表达式
scala> 1 + 1
res0: Int = 2
值
你可以给一个表达式的结果起个名字赋成一个不变量(val)。
scala> val two = 1 + 1
two: Int = 2
变量
如果你需要修改这个名称和结果的绑定,可以选择使用var。
scala> var name = "steve"
name: java.lang.String = steve scala> name = "marius"
name: java.lang.String = marius
函数
你可以使用def创建函数.
scala> def addOne(m: Int): Int = m + 1
addOne: (m: Int)Int
在Scala中,你需要为函数参数指定类型签名。
scala> val three = addOne(2)
three: Int = 3
如果函数不带参数,你可以不写括号。
scala> def three() = 1 + 2
three: ()Int scala> three()
res2: Int = 3 scala> three
res3: Int = 3
匿名函数
你可以创建匿名函数。
scala> (x: Int) => x + 1
res2: (Int) => Int = <function1>
这个函数为名为x的Int变量加1。
scala> res2(1)
res3: Int = 2
你可以传递匿名函数,或将其保存成不变量。
scala> val addOne = (x: Int) => x + 1
addOne: (Int) => Int = <function1> scala> addOne(1)
res4: Int = 2
如果你的函数有很多表达式,可以使用{}来格式化代码,使之易读。
def timesTwo(i: Int): Int = {
println("hello world")
i * 2
}
对匿名函数也是这样的。
scala> { i: Int =>
println("hello world")
i * 2
}
res0: (Int) => Int = <function1>
在将一个匿名函数作为参数进行传递时,这个语法会经常被用到。
部分应用(Partial application)
你可以使用下划线“_”部分应用一个函数,结果将得到另一个函数。Scala使用下划线表示不同上下文中的不同事物,你通常可以把它看作是一个没有命名的神奇通配符。在{ _ + 2 }的上下文中,它代表一个匿名参数。你可以这样使用它:
scala> def adder(m: Int, n: Int) = m + n
adder: (m: Int,n: Int)Int scala> val add2 = adder(2, _:Int)
add2: (Int) => Int = <function1> scala> add2(3)
res50: Int = 5
你可以部分应用参数列表中的任意参数,而不仅仅是最后一个。
柯里化函数
有时会有这样的需求:允许别人一会在你的函数上应用一些参数,然后又应用另外的一些参数。
例如一个乘法函数,在一个场景需要选择乘数,而另一个场景需要选择被乘数。
scala> def multiply(m: Int)(n: Int): Int = m * n
multiply: (m: Int)(n: Int)Int
你可以直接传入两个参数。
scala> multiply(2)(3)
res0: Int = 6
你可以填上第一个参数并且部分应用第二个参数。
scala> val timesTwo = multiply(2) _
timesTwo: (Int) => Int = <function1> scala> timesTwo(3)
res1: Int = 6
你可以对任何多参数函数执行柯里化。例如之前的adder函数
第一次传参是一个加数,返回一个函数,调用第二个函数传参,另一个数,得出结果是和
scala> (adder _).curried
res1: (Int) => (Int) => Int = <function1> scala> res1(2)
res2: (Int) => Int = <function1> scala> res2(3)
res3: Int = 5
可变长度参数
这是一个特殊的语法,可以向方法传入任意多个同类型的参数。例如要在多个字符串上执行String的capitalize函数,可以这样写:
def capitalizeAll(args: String*) = {
args.map { arg =>
arg.capitalize
}
}
scala> capitalizeAll("rarity", "applejack")
res2: Seq[String] = ArrayBuffer(Rarity, Applejack)
类
scala> class Calculator {
| val brand: String = "HP"
| def add(m: Int, n: Int): Int = m + n
| }
defined class Calculator
scala> val calc = new Calculator
calc: Calculator = Calculator@e75a11
scala> calc.add(1, 2)
res1: Int = 3
scala> calc.brand
res2: String = "HP"
上面的例子展示了如何在类中用def定义方法和用val定义字段值。方法就是可以访问类的状态的函数。
构造函数
构造函数不是特殊的方法,他们是除了类的方法定义之外的代码。让我们扩展计算器的例子,增加一个构造函数参数,并用它来初始化内部状态。
class Calculator(brand: String) {
/**
* A constructor.
*/
val color: String = if (brand == "TI") {
"blue"
} else if (brand == "HP") {
"black"
} else {
"white"
}
// An instance method.
def add(m: Int, n: Int): Int = m + n
}
注意两种不同风格的评论。
你可以使用构造函数来构造一个实例:
scala> val calc = new Calculator("HP")
calc: Calculator = Calculator@1e64cc4d
scala> calc.color
res0: String = black
表达式
上文的Calculator例子说明了Scala是如何面向表达式的。颜色的值就是绑定在一个if/else表达式上的。Scala是高度面向表达式的:大多数东西都是表达式而非指令。
旁白: 函数 vs 方法
函数和方法在很大程度上是可以互换的。由于函数和方法是如此的相似,你可能都不知道你调用的东西是一个函数还是一个方法。而当真正碰到的方法和函数之间的差异的时候,你可能会感到困惑。
scala> class C {
| var acc = 0
| def minc = { acc += 1 }
| val finc = { () => acc += 1 }
| }
defined class C
scala> val c = new C
c: C = C@1af1bd6
scala> c.minc // calls c.minc()
scala> c.finc // returns the function as a value:
res2: () => Unit = <function0>
当你可以调用一个不带括号的“函数”,但是对另一个却必须加上括号的时候,你可能会想哎呀,我还以为自己知道Scala是怎么工作的呢。也许他们有时需要括号?你可能以为自己用的是函数,但实际使用的是方法。
在实践中,即使不理解方法和函数上的区别,你也可以用Scala做伟大的事情。如果你是Scala新手,而且在读两者的差异解释,你可能会跟不上。不过这并不意味着你在使用Scala上有麻烦。它只是意味着函数和方法之间的差异是很微妙的,只有深入语言内部才能清楚理解它。
继承
class ScientificCalculator(brand: String) extends Calculator(brand) {
def log(m: Double, base: Double) = math.log(m) / math.log(base)
}
参考 Effective Scala 指出如果子类与父类实际上没有区别,类型别名是优于继承的。A Tour of Scala 详细介绍了子类化。
重载方法
class EvenMoreScientificCalculator(brand: String) extends ScientificCalculator(brand) {
def log(m: Int): Double = log(m, math.exp(1))
}
抽象类
你可以定义一个抽象类,它定义了一些方法但没有实现它们。取而代之是由扩展抽象类的子类定义这些方法。你不能创建抽象类的实例。
scala> abstract class Shape {
| def getArea():Int // subclass should define this
| }
defined class Shape
scala> class Circle(r: Int) extends Shape {
| def getArea():Int = { r * r * 3 }
| }
defined class Circle
scala> val s = new Shape
<console>:8: error: class Shape is abstract; cannot be instantiated
val s = new Shape
^
scala> val c = new Circle(2)
c: Circle = Circle@65c0035b
特质(Traits)
特质是一些字段和行为的集合,可以扩展或混入(mixin)你的类中。
trait Car {
val brand: String
}
trait Shiny {
val shineRefraction: Int
}
class BMW extends Car {
val brand = "BMW"
}
通过with关键字,一个类可以扩展多个特质:
class BMW extends Car with Shiny {
val brand = "BMW"
val shineRefraction = 12
}
参考 Effective Scala 对特质的观点。
什么时候应该使用特质而不是抽象类? 如果你想定义一个类似接口的类型,你可能会在特质和抽象类之间难以取舍。这两种形式都可以让你定义一个类型的一些行为,并要求继承者定义一些其他行为。一些经验法则:
- 优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。
- 如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行。例如,你不能说
trait t(i: Int) {},参数i是非法的。
你不是问这个问题的第一人。可以查看更全面的答案: stackoverflow: Scala特质 vs 抽象类 , 抽象类和特质的区别, and Scala编程: 用特质,还是不用特质?
类型
此前,我们定义了一个函数的参数为Int,表示输入是一个数字类型。其实函数也可以是泛型的,来适用于所有类型。当这种情况发生时,你会看到用方括号语法引入的类型参数。下面的例子展示了一个使用泛型键和值的缓存。
trait Cache[K, V] {
def get(key: K): V
def put(key: K, value: V)
def delete(key: K)
}
方法也可以引入类型参数。
def remove[K](key: K)
=======================================================================================================================================================================
apply 方法
当类或对象有一个主要用途的时候,apply方法为你提供了一个很好的语法糖。
scala> class Foo {}
defined class Foo
scala> object FooMaker {
| def apply() = new Foo
| }
defined module FooMaker
scala> val newFoo = FooMaker()
newFoo: Foo = Foo@5b83f762
或
scala> class Bar {
| def apply() = 0
| }
defined class Bar
scala> val bar = new Bar
bar: Bar = Bar@47711479
scala> bar()
res8: Int = 0
在这里,我们实例化对象看起来像是在调用一个方法。以后会有更多介绍!
单例对象
单例对象用于持有一个类的唯一实例。通常用于工厂模式。
object Timer {
var count = 0
def currentCount(): Long = {
count += 1
count
}
}
可以这样使用:
scala> Timer.currentCount()
res0: Long = 1
单例对象可以和类具有相同的名称,此时该对象也被称为“伴生对象”。我们通常将伴生对象作为工厂使用。
下面是一个简单的例子,可以不需要使用’new’来创建一个实例了。Apply
class Bar(foo: String)
object Bar {
def apply(foo: String) = new Bar(foo)
}
函数即对象
在Scala中,我们经常谈论对象的函数式编程。这是什么意思?到底什么是函数呢?
函数是一些特质的集合。具体来说,具有一个参数的函数是Function1特质的一个实例。这个特征定义了apply()语法糖,让你调用一个对象时就像你在调用一个函数。
scala> object addOne extends Function1[Int, Int] {
| def apply(m: Int): Int = m + 1
| }
defined module addOne
scala> addOne(1)
res2: Int = 2
这个Function特质集合下标从0开始一直到22。为什么是22?这是一个主观的魔幻数字(magic number)。我从来没有使用过多于22个参数的函数,所以这个数字似乎是合理的。
apply语法糖有助于统一对象和函数式编程的二重性。你可以传递类,并把它们当做函数使用,而函数本质上是类的实例。
这是否意味着,当你在类中定义一个方法时,得到的实际上是一个Function*的实例?不是的,在类中定义的方法是方法而不是函数。在repl中独立定义的方法是Function*的实例。
类也可以扩展Function,这些类的实例可以使用()调用。
scala> class AddOne extends Function1[Int, Int] {
| def apply(m: Int): Int = m + 1
| }
defined class AddOne
scala> val plusOne = new AddOne()
plusOne: AddOne = <function1>
scala> plusOne(1)
res0: Int = 2
可以使用更直观快捷的extends (Int => Int)代替extends Function1[Int, Int]
class AddOne extends (Int => Int) {
def apply(m: Int): Int = m + 1
}
包
你可以将代码组织在包里。
package com.twitter.example
在文件头部定义包,会将文件中所有的代码声明在那个包中。
值和函数不能在类或单例对象之外定义。单例对象是组织静态函数(static function)的有效工具。
package com.twitter.example
object colorHolder {
val BLUE = "Blue"
val RED = "Red"
}
现在你可以直接访问这些成员
println("the color is: " + com.twitter.example.colorHolder.BLUE)
注意在你定义这个对象时Scala解释器的返回:
scala> object colorHolder {
| val Blue = "Blue"
| val Red = "Red"
| }
defined module colorHolder
这暗示了Scala的设计者是把对象作为Scala的模块系统的一部分进行设计的。
模式匹配
这是Scala中最有用的部分之一。
匹配值
val times = 1
times match {
case 1 => "one"
case 2 => "two"
case _ => "some other number"
}
使用守卫进行匹配
times match {
case i if i == 1 => "one"
case i if i == 2 => "two"
case _ => "some other number"
}
注意我们是怎样将值赋给变量’i’的。
在最后一行指令中的_是一个通配符;它保证了我们可以处理所有的情况。
否则当传进一个不能被匹配的数字的时候,你将获得一个运行时错误。我们以后会继续讨论这个话题的。
参考 Effective Scala 对什么时候使用模式匹配 和 模式匹配格式化的建议. A Tour of Scala 也描述了 模式匹配
匹配类型
你可以使用 match来分别处理不同类型的值。
def bigger(o: Any): Any = {
o match {
case i: Int if i < 0 => i - 1
case i: Int => i + 1
case d: Double if d < 0.0 => d - 0.1
case d: Double => d + 0.1
case text: String => text + "s"
}
}
匹配类成员
还记得我们之前的计算器吗。
让我们通过类型对它们进行分类。
一开始会很痛苦。
def calcType(calc: Calculator) = calc match {
case _ if calc.brand == "hp" && calc.model == "20B" => "financial"
case _ if calc.brand == "hp" && calc.model == "48G" => "scientific"
case _ if calc.brand == "hp" && calc.model == "30B" => "business"
case _ => "unknown"
}
(⊙o⊙)哦,太痛苦了。幸好Scala提供了一些应对这种情况的有效工具。
样本类 Case Classes
使用样本类可以方便得存储和匹配类的内容。你不用new关键字就可以创建它们。
scala> case class Calculator(brand: String, model: String)
defined class Calculator scala> val hp20b = Calculator("hp", "20b")
hp20b: Calculator = Calculator(hp,20b)
样本类基于构造函数的参数,自动地实现了相等性和易读的toString方法。
scala> val hp20b = Calculator("hp", "20b")
hp20b: Calculator = Calculator(hp,20b)
scala> val hp20B = Calculator("hp", "20b")
hp20B: Calculator = Calculator(hp,20b)
scala> hp20b == hp20B
res6: Boolean = true
样本类也可以像普通类那样拥有方法。
使用样本类进行模式匹配
case classes are designed to be used with pattern matching. Let’s simplify our calculator classifier example from earlier.
样本类就是被设计用在模式匹配中的。让我们简化之前的计算器分类器的例子。
val hp20b = Calculator("hp", "20B")
val hp30b = Calculator("hp", "30B")
def calcType(calc: Calculator) = calc match {
case Calculator("hp", "20B") => "financial"
case Calculator("hp", "48G") => "scientific"
case Calculator("hp", "30B") => "business"
case Calculator(ourBrand, ourModel) => "Calculator: %s %s is of unknown type".format(ourBrand, ourModel)
}
最后一句也可以这样写
case Calculator(_, _) => "Calculator of unknown type"
或者我们完全可以不将匹配对象指定为Calculator类型
case _ => "Calculator of unknown type"
或者我们也可以将匹配的值重新命名。
case c@Calculator(_, _) => "Calculator: %s of unknown type".format(c)
异常
Scala中的异常可以在try-catch-finally语法中通过模式匹配使用。
try {
remoteCalculatorService.add(1, 2)
} catch {
case e: ServerIsDownException => log.error(e, "the remote calculator service is unavailable. should have kept your trusty HP.")
} finally {
remoteCalculatorService.close()
}
try也是面向表达式的
val result: Int = try {
remoteCalculatorService.add(1, 2)
} catch {
case e: ServerIsDownException => {
log.error(e, "the remote calculator service is unavailable. should have kept your trusty HP.")
0
}
} finally {
remoteCalculatorService.close()
}
这并不是一个完美编程风格的展示,而只是一个例子,用来说明try-catch-finally和Scala中其他大部分事物一样是表达式。
当一个异常被捕获处理了,finally块将被调用;它不是表达式的一部分 ====================================================================================================================================================
列表 List
scala> val numbers = List(1, 2, 3, 4)
numbers: List[Int] = List(1, 2, 3, 4)
集 Set
集没有重复
scala> Set(1, 1, 2)
res0: scala.collection.immutable.Set[Int] = Set(1, 2)
元组 Tuple
元组是在不使用类的前提下,将元素组合起来形成简单的逻辑集合。
scala> val hostPort = ("localhost", 80)
hostPort: (String, Int) = (localhost, 80)
与样本类不同,元组不能通过名称获取字段,而是使用位置下标来读取对象;而且这个下标基于1,而不是基于0。
scala> hostPort._1
res0: String = localhost scala> hostPort._2
res1: Int = 80
元组可以很好得与模式匹配相结合。
hostPort match {
case ("localhost", port) => ...
case (host, port) => ...
}
在创建两个元素的元组时,可以使用特殊语法:->
scala> 1 -> 2
res0: (Int, Int) = (1,2)
参考 Effective Scala 对 解构绑定 (“拆解”一个元组)的观点。
映射 Map
它可以持有基本数据类型。
Map(1 -> 2)
Map("foo" -> "bar")
这看起来像是特殊的语法,不过不要忘了上文讨论的->可以用来创建二元组。
Map()方法也使用了从第一节课学到的变参列表:Map(1 -> "one", 2 -> "two")将变为 Map((1, "one"), (2, "two")),其中第一个参数是映射的键,第二个参数是映射的值。
映射的值可以是映射甚或是函数。
Map(1 -> Map("foo" -> "bar"))
Map("timesTwo" -> { timesTwo(_) })
选项 Option
Option 是一个表示有可能包含值的容器。
Option基本的接口是这样的:
trait Option[T] {
def isDefined: Boolean
def get: T
def getOrElse(t: T): T
}
Option本身是泛型的,并且有两个子类: Some[T] 或 None
我们看一个使用Option的例子:
Map.get 使用 Option 作为其返回值,表示这个方法也许不会返回你请求的值。
scala> val numbers = Map("one" -> 1, "two" -> 2)
numbers: scala.collection.immutable.Map[java.lang.String,Int] = Map(one -> 1, two -> 2)
scala> numbers.get("two")
res0: Option[Int] = Some(2)
scala> numbers.get("three")
res1: Option[Int] = None
现在我们的数据似乎陷在Option中了,我们怎样获取这个数据呢?
直觉上想到的可能是在isDefined方法上使用条件判断来处理。
// We want to multiply the number by two, otherwise return 0.
val result = if (res1.isDefined) {
res1.get * 2
} else {
0
}
我们建议使用getOrElse或模式匹配处理这个结果。
getOrElse 让你轻松地定义一个默认值。
val result = res1.getOrElse(0) * 2
模式匹配能自然地配合Option使用。
val result = res1 match {
case Some(n) => n * 2
case None => 0
}
参考 Effective Scala 对使用Options的意见。
函数组合子(Functional Combinators)
List(1, 2, 3) map squared对列表中的每一个元素都应用了squared平方函数,并返回一个新的列表List(1, 4, 9)。我们称这个操作map 组合子。 (如果想要更好的定义,你可能会喜欢Stackoverflow上对组合子的说明。)他们常被用在标准的数据结构上。
map
map对列表中的每个元素应用一个函数,返回应用后的元素所组成的列表。
scala> val numbers = List(1, 2, 3, 4)
scala> numbers.map((i: Int) => i * 2)
res0: List[Int] = List(2, 4, 6, 8)
或传入一个部分应用函数
scala> def timesTwo(i: Int): Int = i * 2
timesTwo: (i: Int)Int scala> numbers.map(timesTwo _)
res0: List[Int] = List(2, 4, 6, 8)
foreach
foreach很像map,但没有返回值。foreach仅用于有副作用[side-effects]的函数。
scala> numbers.foreach((i: Int) => i * 2)
什么也没有返回。
你可以尝试存储返回值,但它会是Unit类型(即void)
scala> val doubled = numbers.foreach((i: Int) => i * 2)
doubled: Unit = ()
filter
filter移除任何对传入函数计算结果为false的元素。返回一个布尔值的函数通常被称为谓词函数[或判定函数]。
scala> numbers.filter((i: Int) => i % 2 == 0)
res0: List[Int] = List(2, 4) scala> def isEven(i: Int): Boolean = i % 2 == 0
isEven: (i: Int)Boolean scala> numbers.filter(isEven _)
res2: List[Int] = List(2, 4)
zip
zip将两个列表的内容聚合到一个对偶列表中。
scala> List(1, 2, 3).zip(List("a", "b", "c"))
res0: List[(Int, String)] = List((1,a), (2,b), (3,c))
partition
partition将使用给定的谓词函数分割列表。
scala> val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> numbers.partition(_ % 2 == 0)
res0: (List[Int], List[Int]) = (List(2, 4, 6, 8, 10),List(1, 3, 5, 7, 9))
find
find返回集合中第一个匹配谓词函数的元素。
scala> numbers.find((i: Int) => i > 5)
res0: Option[Int] = Some(6)
drop & dropWhile
drop 将删除前i个元素
scala> numbers.drop(5)
res0: List[Int] = List(6, 7, 8, 9, 10)
dropWhile 将删除元素直到找到第一个匹配谓词函数的元素。例如,如果我们在numbers列表上使用dropWhile奇数的函数, 1将被丢弃(但3不会被丢弃,因为他被2“保护”了)。
scala> numbers.dropWhile(_ % 2 != 0)
res0: List[Int] = List(2, 3, 4, 5, 6, 7, 8, 9, 10)
foldLeft
scala> numbers.foldLeft(0)((m: Int, n: Int) => m + n)
res0: Int = 55
0为初始值(记住numbers是List[Int]类型),m作为一个累加器。
直接观察运行过程:
scala> numbers.foldLeft(0) { (m: Int, n: Int) => println("m: " + m + " n: " + n); m + n }
m: 0 n: 1
m: 1 n: 2
m: 3 n: 3
m: 6 n: 4
m: 10 n: 5
m: 15 n: 6
m: 21 n: 7
m: 28 n: 8
m: 36 n: 9
m: 45 n: 10
res0: Int = 55
foldRight
和foldLeft一样,只是运行过程相反。
scala> numbers.foldRight(0) { (m: Int, n: Int) => println("m: " + m + " n: " + n); m + n }
m: 10 n: 0
m: 9 n: 10
m: 8 n: 19
m: 7 n: 27
m: 6 n: 34
m: 5 n: 40
m: 4 n: 45
m: 3 n: 49
m: 2 n: 52
m: 1 n: 54
res0: Int = 55
flatten
flatten将嵌套结构扁平化为一个层次的集合。
scala> List(List(1, 2), List(3, 4)).flatten
res0: List[Int] = List(1, 2, 3, 4)
flatMap
flatMap是一种常用的组合子,结合映射[mapping]和扁平化[flattening]。 flatMap需要一个处理嵌套列表的函数,然后将结果串连起来。
scala> val nestedNumbers = List(List(1, 2), List(3, 4))
nestedNumbers: List[List[Int]] = List(List(1, 2), List(3, 4)) scala> nestedNumbers.flatMap(x => x.map(_ * 2))
res0: List[Int] = List(2, 4, 6, 8)
可以把它看做是“先映射后扁平化”的快捷操作:
scala> nestedNumbers.map((x: List[Int]) => x.map(_ * 2)).flatten
res1: List[Int] = List(2, 4, 6, 8)
这个例子先调用map,然后可以马上调用flatten,这就是“组合子”的特征,也是这些函数的本质。
参考 Effective Scala 对flatMap的意见。
扩展函数组合子
现在我们已经学过集合上的一些函数。
我们将尝试写自己的函数组合子。
有趣的是,上面所展示的每一个函数组合子都可以用fold方法实现。让我们看一些例子。
def ourMap(numbers: List[Int], fn: Int => Int): List[Int] = {
numbers.foldRight(List[Int]()) { (x: Int, xs: List[Int]) =>
fn(x) :: xs
}
}
scala> ourMap(numbers, timesTwo(_))
res0: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
为什么是List[Int]()?Scala没有聪明到理解你的目的是将结果积聚在一个空的Int类型的列表中。
Map?
所有展示的函数组合子都可以在Map上使用。Map可以被看作是一个二元组的列表,所以你写的函数要处理一个键和值的二元组。
scala> val extensions = Map("steve" -> 100, "bob" -> 101, "joe" -> 201)
extensions: scala.collection.immutable.Map[String,Int] = Map((steve,100), (bob,101), (joe,201))
现在筛选出电话分机号码低于200的条目。
scala> extensions.filter((namePhone: (String, Int)) => namePhone._2 < 200)
res0: scala.collection.immutable.Map[String,Int] = Map((steve,100), (bob,101))
因为参数是元组,所以你必须使用位置获取器来读取它们的键和值。呃!
幸运的是,我们其实可以使用模式匹配更优雅地提取键和值。
scala> extensions.filter({case (name, extension) => extension < 200})
res0: scala.collection.immutable.Map[String,Int] = Map((steve,100), (bob,101))
====================================================================================================
函数组合
让我们创建两个函数:
scala> def f(s: String) = "f(" + s + ")"
f: (String)java.lang.String
scala> def g(s: String) = "g(" + s + ")"
g: (String)java.lang.String
compose
compose 组合其他函数形成一个新的函数 f(g(x))
scala> val fComposeG = f _ compose g _
fComposeG: (String) => java.lang.String = <function> scala> fComposeG("yay")
res0: java.lang.String = f(g(yay))
andThen
andThen 和 compose很像,但是调用顺序是先调用第一个函数,然后调用第二个,即g(f(x))
scala> val fAndThenG = f _ andThen g _
fAndThenG: (String) => java.lang.String = <function> scala> fAndThenG("yay")
res1: java.lang.String = g(f(yay))
柯里化 vs 偏应用
case 语句
那么究竟什么是case语句?
这是一个名为PartialFunction的函数的子类。
多个case语句的集合是什么?
他们是共同组合在一起的多个PartialFunction。
理解PartialFunction(偏函数)
对给定的输入参数类型,函数可接受该类型的任何值。换句话说,一个(Int) => String 的函数可以接收任意Int值,并返回一个字符串。
对给定的输入参数类型,偏函数只能接受该类型的某些特定的值。一个定义为(Int) => String 的偏函数可能不能接受所有Int值为输入。
isDefinedAt 是PartialFunction的一个方法,用来确定PartialFunction是否能接受一个给定的参数。
注意 偏函数PartialFunction 和我们前面提到的部分应用函数是无关的。
参考 Effective Scala 对PartialFunction的意见。
scala> val one: PartialFunction[Int, String] = { case 1 => "one" }
one: PartialFunction[Int,String] = <function1>
scala> one.isDefinedAt(1)
res0: Boolean = true
scala> one.isDefinedAt(2)
res1: Boolean = false
您可以调用一个偏函数。
scala> one(1)
res2: String = one
PartialFunctions可以使用orElse组成新的函数,得到的PartialFunction反映了是否对给定参数进行了定义。
scala> val two: PartialFunction[Int, String] = { case 2 => "two" }
two: PartialFunction[Int,String] = <function1>
scala> val three: PartialFunction[Int, String] = { case 3 => "three" }
three: PartialFunction[Int,String] = <function1>
scala> val wildcard: PartialFunction[Int, String] = { case _ => "something else" }
wildcard: PartialFunction[Int,String] = <function1>
scala> val partial = one orElse two orElse three orElse wildcard
partial: PartialFunction[Int,String] = <function1>
scala> partial(5)
res24: String = something else
scala> partial(3)
res25: String = three
scala> partial(2)
res26: String = two
scala> partial(1)
res27: String = one
scala> partial(0)
res28: String = something else
case 之谜
上周我们看到一些新奇的东西。我们在通常应该使用函数的地方看到了一个case语句。
scala> case class PhoneExt(name: String, ext: Int)
defined class PhoneExt scala> val extensions = List(PhoneExt("steve", 100), PhoneExt("robey", 200))
extensions: List[PhoneExt] = List(PhoneExt(steve,100), PhoneExt(robey,200)) scala> extensions.filter { case PhoneExt(name, extension) => extension < 200 }
res0: List[PhoneExt] = List(PhoneExt(steve,100))
为什么这段代码可以工作?
filter使用一个函数。在这个例子中是一个谓词函数(PhoneExt) => Boolean。
PartialFunction是Function的子类型,所以filter也可以使用PartialFunction!
===========================================================================================================
什么是静态类型?它们为什么有用?
按Pierce的话讲:“类型系统是一个语法方法,它们根据程序计算的值的种类对程序短语进行分类,通过分类结果错误行为进行自动检查。”
类型允许你表示函数的定义域和值域。例如,从数学角度看这个定义:
f: R -> N
它告诉我们函数“f”是从实数集到自然数集的映射。
抽象地说,这就是 具体 类型的准确定义。类型系统给我们提供了一些更强大的方式来表达这些集合。
鉴于这些注释,编译器可以 静态地 (在编译时)验证程序是 合理 的。也就是说,如果值(在运行时)不符合程序规定的约束,编译将失败。
一般说来,类型检查只能保证 不合理 的程序不能编译通过。它不能保证每一个合理的程序都 可以 编译通过。
随着类型系统表达能力的提高,我们可以生产更可靠的代码,因为它能够在我们运行程序之前验证程序的不变性(当然是发现类型本身的模型bug!)。学术界一直很努力地提高类型系统的表现力,包括值依赖(value-dependent)类型!
需要注意的是,所有的类型信息会在编译时被删去,因为它已不再需要。这就是所谓的擦除。
Scala中的类型
Scala强大的类型系统拥有非常丰富的表现力。其主要特性有:
- 参数化多态性 粗略地说,就是泛型编程
- (局部)类型推断 粗略地说,就是为什么你不需要这样写代码
val i: Int = 12: Int - 存在量化 粗略地说,为一些没有名称的类型进行定义
- 视窗 我们将下周学习这些;粗略地说,就是将一种类型的值“强制转换”为另一种类型
参数化多态性
多态性是在不影响静态类型丰富性的前提下,用来(给不同类型的值)编写通用代码的。
例如,如果没有参数化多态性,一个通用的列表数据结构总是看起来像这样(事实上,它看起来很像使用泛型前的Java):
scala> 2 :: 1 :: "bar" :: "foo" :: Nil
res5: List[Any] = List(2, 1, bar, foo)
现在我们无法恢复其中成员的任何类型信息。
scala> res5.head
res6: Any = 2
所以我们的应用程序将会退化为一系列类型转换(“asInstanceOf[]”),并且会缺乏类型安全的保障(因为这些都是动态的)。
多态性是通过指定 类型变量 实现的。
scala> def drop1[A](l: List[A]) = l.tail
drop1: [A](l: List[A])List[A] scala> drop1(List(1,2,3))
res1: List[Int] = List(2, 3)
Scala有秩1多态性
粗略地说,这意味着在Scala中,有一些你想表达的类型概念“过于泛化”以至于编译器无法理解。假设你有一个函数
def toList[A](a: A) = List(a)
你希望继续泛型地使用它:
def foo[A, B](f: A => List[A], b: B) = f(b)
这段代码不能编译,因为所有的类型变量只有在调用上下文中才被固定。即使你“钉住”了类型B:
def foo[A](f: A => List[A], i: Int) = f(i)
…你也会得到一个类型不匹配的错误。
类型推断
静态类型的一个传统反对意见是,它有大量的语法开销。Scala通过 类型推断 来缓解这个问题。
在函数式编程语言中,类型推断的经典方法是 Hindley Milner算法,它最早是实现在ML中的。
Scala类型推断系统的实现稍有不同,但本质类似:推断约束,并试图统一类型。
例如,在Scala中你无法这样做:
scala> { x => x }
<console>:7: error: missing parameter type
{ x => x }
而在OCaml中你可以:
# fun x -> x;;
- : 'a -> 'a = <fun>
在Scala中所有类型推断是 局部的 。Scala一次分析一个表达式。例如:
scala> def id[T](x: T) = x
id: [T](x: T)T scala> val x = id(322)
x: Int = 322 scala> val x = id("hey")
x: java.lang.String = hey scala> val x = id(Array(1,2,3,4))
x: Array[Int] = Array(1, 2, 3, 4)
类型信息都保存完好,Scala编译器为我们进行了类型推断。请注意我们并不需要明确指定返回类型。
变性 Variance
Scala的类型系统必须同时解释类层次和多态性。类层次结构可以表达子类关系。在混合OO和多态性时,一个核心问题是:如果T’是T一个子类,Container[T’]应该被看做是Container[T]的子类吗?变性(Variance)注解允许你表达类层次结构和多态类型之间的关系:
| 含义 | Scala 标记 | |
| 协变covariant | C[T’]是 C[T] 的子类 | [+T] |
| 逆变contravariant | C[T] 是 C[T’]的子类 | [-T] |
| 不变invariant | C[T] 和 C[T’]无关 | [T] |
子类型关系的真正含义:对一个给定的类型T,如果T’是其子类型,你能替换它吗?
scala> class Covariant[+A]
defined class Covariant scala> val cv: Covariant[AnyRef] = new Covariant[String]
cv: Covariant[AnyRef] = Covariant@4035acf6 scala> val cv: Covariant[String] = new Covariant[AnyRef]
<console>:6: error: type mismatch;
found : Covariant[AnyRef]
required: Covariant[String]
val cv: Covariant[String] = new Covariant[AnyRef]
^ scala> class Contravariant[-A]
defined class Contravariant scala> val cv: Contravariant[String] = new Contravariant[AnyRef]
cv: Contravariant[AnyRef] = Contravariant@49fa7ba scala> val fail: Contravariant[AnyRef] = new Contravariant[String]
<console>:6: error: type mismatch;
found : Contravariant[String]
required: Contravariant[AnyRef]
val fail: Contravariant[AnyRef] = new Contravariant[String]
^
逆变似乎很奇怪。什么时候才会用到它呢?令人惊讶的是,函数特质的定义就使用了它!
trait Function1 [-T1, +R] extends AnyRef
如果你仔细从替换的角度思考一下,会发现它是非常合理的。让我们先定义一个简单的类层次结构:
scala> class Animal { val sound = "rustle" }
defined class Animal
scala> class Bird extends Animal { override val sound = "call" }
defined class Bird
scala> class Chicken extends Bird { override val sound = "cluck" }
defined class Chicken
假设你需要一个以Bird为参数的函数:
scala> val getTweet: (Bird => String) = // TODO
标准动物库有一个函数满足了你的需求,但它的参数是Animal。在大多数情况下,如果你说“我需要一个___,我有一个___的子类”是可以的。但是,在函数参数这里是逆变的。如果你需要一个接受参数类型Bird的函数变量,但却将这个变量指向了接受参数类型为Chicken的函数,那么给它传入一个Duck时就会出错。然而,如果将该变量指向一个接受参数类型为Animal的函数就不会有这种问题:
scala> val getTweet: (Bird => String) = ((a: Animal) => a.sound )
getTweet: Bird => String = <function1>
函数的返回值类型是协变的。如果你需要一个返回Bird的函数,但指向的函数返回类型是Chicken,这当然是可以的。
scala> val hatch: (() => Bird) = (() => new Chicken )
hatch: () => Bird = <function0>
边界
Scala允许你通过 边界 来限制多态变量。这些边界表达了子类型关系。
scala> def cacophony[T](things: Seq[T]) = things map (_.sound)
<console>:7: error: value sound is not a member of type parameter T
def cacophony[T](things: Seq[T]) = things map (_.sound)
^ scala> def biophony[T <: Animal](things: Seq[T]) = things map (_.sound)
biophony: [T <: Animal](things: Seq[T])Seq[java.lang.String] scala> biophony(Seq(new Chicken, new Bird))
res5: Seq[java.lang.String] = List(cluck, call)
类型下界也是支持的,这让逆变和巧妙协变的引入得心应手。List[+T]是协变的;一个Bird的列表也是Animal的列表。List定义一个操作::(elem T)返回一个加入了elem的新的List。新的List和原来的列表具有相同的类型:
scala> val flock = List(new Bird, new Bird)
flock: List[Bird] = List(Bird@7e1ec70e, Bird@169ea8d2) scala> new Chicken :: flock
res53: List[Bird] = List(Chicken@56fbda05, Bird@7e1ec70e, Bird@169ea8d2)
List 同样 定义了::[B >: T](x: B) 来返回一个List[B]。请注意B >: T,这指明了类型B为类型T的超类。这个方法让我们能够做正确地处理在一个List[Bird]前面加一个Animal的操作:
scala> new Animal :: flock
res59: List[Animal] = List(Animal@11f8d3a8, Bird@7e1ec70e, Bird@169ea8d2)
注意返回类型是Animal。
量化
有时候,你并不关心是否能够命名一个类型变量,例如:
scala> def count[A](l: List[A]) = l.size
count: [A](List[A])Int
这时你可以使用“通配符”取而代之:
scala> def count(l: List[_]) = l.size
count: (List[_])Int
这相当于是下面代码的简写:
scala> def count(l: List[T forSome { type T }]) = l.size
count: (List[T forSome { type T }])Int
注意量化会的结果会变得非常难以理解:
scala> def drop1(l: List[_]) = l.tail
drop1: (List[_])List[Any]
突然,我们失去了类型信息!让我们细化代码看看发生了什么:
scala> def drop1(l: List[T forSome { type T }]) = l.tail
drop1: (List[T forSome { type T }])List[T forSome { type T }]
我们不能使用T因为类型不允许这样做。
你也可以为通配符类型变量应用边界:
scala> def hashcodes(l: Seq[_ <: AnyRef]) = l map (_.hashCode)
hashcodes: (Seq[_ <: AnyRef])Seq[Int] scala> hashcodes(Seq(1,2,3))
<console>:7: error: type mismatch;
found : Int(1)
required: AnyRef
Note: primitive types are not implicitly converted to AnyRef.
You can safely force boxing by casting x.asInstanceOf[AnyRef].
hashcodes(Seq(1,2,3))
^ scala> hashcodes(Seq("one", "two", "three"))
res1: Seq[Int] = List(110182, 115276, 110339486)
========================================================================================================================
视界(“类型类”)
有时候,你并不需要指定一个类型是等/子/超于另一个类,你可以通过转换这个类来伪装这种关联关系。一个视界指定一个类型可以被“看作是”另一个类型。这对对象的只读操作是很有用的。
隐 函数允许类型自动转换。更确切地说,在隐式函数可以帮助满足类型推断时,它们允许按需的函数应用。例如:
scala> implicit def strToInt(x: String) = x.toInt
strToInt: (x: String)Int scala> ""
res0: java.lang.String = 123 scala> val y: Int = ""
y: Int = 123 scala> math.max("", 111)
res1: Int = 123
视界,就像类型边界,要求对给定的类型存在这样一个函数。您可以使用<%指定类型限制,例如:
scala> class Container[A <% Int] { def addIt(x: A) = 123 + x }
defined class Container
这是说 A 必须“可被视”为 Int 。让我们试试。
scala> (new Container[String]).addIt("")
res11: Int = 246
scala> (new Container[Int]).addIt(123)
res12: Int = 246
scala> (new Container[Float]).addIt(123.2F)
<console>:8: error: could not find implicit value for evidence parameter of type (Float) => Int
(new Container[Float]).addIt(123.2)
^
其他类型限制
方法可以通过隐含参数执行更复杂的类型限制。例如,List支持对数字内容执行sum,但对其他内容却不行。可是Scala的数字类型并不都共享一个超类,所以我们不能使用T <: Number。相反,要使之能工作,Scala的math库对适当的类型T 定义了一个隐含的Numeric[T]。 然后在List定义中使用它:
sum[B >: A](implicit num: Numeric[B]): B
如果你调用List(1,2).sum(),你并不需要传入一个 num 参数;它是隐式设置的。但如果你调用List("whoop").sum(),它会抱怨无法设置num。
在没有设定陌生的对象为Numeric的时候,方法可能会要求某种特定类型的“证据”。这时可以使用以下类型-关系运算符:
| A =:= B | A 必须和 B相等 |
| A <:< B | A 必须是 B的子类 |
| A <%< B | A 必须可以被看做是 B |
scala> class Container[A](value: A) { def addIt(implicit evidence: A =:= Int) = 123 + value }
defined class Container
scala> (new Container(123)).addIt
res11: Int = 246
scala> (new Container("")).addIt
<console>:10: error: could not find implicit value for parameter evidence: =:=[java.lang.String,Int]
类似地,根据之前的隐式转换,我们可以放松约束为可视性:
验证报错:
aaarticlea/png;base64,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" alt="" />
scala> class Container[A](value: A) { def addIt(implicit evidence: A <%< Int) = 123 + value }
defined class Container
scala> (new Container("")).addIt
res15: Int = 246
使用视图进行泛型编程
在Scala标准库中,视图主要用于实现集合的通用函数。例如“min”函数(在 Seq[] 上)就使用了这种技术:
def min[B >: A](implicit cmp: Ordering[B]): A = {
if (isEmpty)
throw new UnsupportedOperationException("empty.min")
reduceLeft((x, y) => if (cmp.lteq(x, y)) x else y)
}
其主要优点是:
- 集合中的元素并不是必须实现 Ordered 特质,但 Ordered 的使用仍然可以执行静态类型检查。
- 无需任何额外的库支持,你也可以定义自己的排序:
scala> List(1,2,3,4).min
res0: Int = 1 scala> List(1,2,3,4).min(new Ordering[Int] { def compare(a: Int, b: Int) = b compare a })
res3: Int = 4
作为旁注,标准库中有视图来将 Ordered 转换为 Ordering (反之亦然)。
trait LowPriorityOrderingImplicits {
implicit def ordered[A <: Ordered[A]]: Ordering[A] = new Ordering[A] {
def compare(x: A, y: A) = x.compare(y)
}
}
上下文边界和implicitly[]
Scala2.8引入了一种串联和访问隐式参数的快捷方式。
scala> def foo[A](implicit x: Ordered[A]) {}
foo: [A](implicit x: Ordered[A])Unit
scala> def foo[A : Ordered] {}
foo: [A](implicit evidence$1: Ordered[A])Unit
隐式值可能会通过 implicitly 被访问
scala> implicitly[Ordering[Int]]
res37: Ordering[Int] = scala.math.Ordering$Int$@3a9291cf
相结合后往往会使用更少的代码,尤其是串联视图的时候。
更高级多态性类型 和 特设多态性
Scala可以对“更高阶”的类型进行抽象。例如,假设您需要用几种类型的容器处理几种类型的数据。你可能定义了一个Container的接口,它可以被实现为几种类型的容器:Option、List等。你要定义可以使用这些容器里的值的接口,但不想确定值的类型。
这类似与函数柯里化。例如,尽管“一元类型”有类似List[A]的构造函数,这意味着我们必须满足一个“级别”的类型变量来产生一个具体的类型(就像一个没有柯里化的函数需要只提供一个参数列表来被调用),更高阶的类型需要更多。
scala> trait Container[M[_]] { def put[A](x: A): M[A]; def get[A](m: M[A]): A }
scala> val container = new Container[List] { def put[A](x: A) = List(x); def get[A](m: List[A]) = m.head }
container: java.lang.Object with Container[List] = $anon$1@7c8e3f75
scala> container.put("hey")
res24: List[java.lang.String] = List(hey)
scala> container.put(123)
res25: List[Int] = List(123)
注意:*Container*是参数化类型的多态(“容器类型”)。
如果我们结合隐式转换implicits使用容器,我们会得到“特设的”多态性:即对容器写泛型函数的能力。
scala> trait Container[M[_]] { def put[A](x: A): M[A]; def get[A](m: M[A]): A }
scala> implicit val listContainer = new Container[List] { def put[A](x: A) = List(x); def get[A](m: List[A]) = m.head }
scala> implicit val optionContainer = new Container[Some] { def put[A](x: A) = Some(x); def get[A](m: Some[A]) = m.get }
scala> def tupleize[M[_]: Container, A, B](fst: M[A], snd: M[B]) = {
| val c = implicitly[Container[M]]
| c.put(c.get(fst), c.get(snd))
| }
tupleize: [M[_],A,B](fst: M[A],snd: M[B])(implicit evidence$1: Container[M])M[(A, B)]
scala> tupleize(Some(1), Some(2))
res33: Some[(Int, Int)] = Some((1,2))
scala> tupleize(List(1), List(2))
res34: List[(Int, Int)] = List((1,2))
F-界多态性
通常有必要来访问一个(泛型)特质的具体子类。例如,想象你有一些泛型特质,但需要可以与它的某一子类进行比较。
trait Container extends Ordered[Container]
然而,现在比较方法是必须的了(在class中定义)
def compare(that: Container): Int
因此,我们不能访问具体子类型,例如:
class MyContainer extends Container {
def compare(that: MyContainer): Int
}
编译失败,因为我们对 Container 指定了Ordered特质,而不是对特定子类型指定的。
为了调和这一点,我们改用F-界的多态性。
trait Container[A <: Container[A]] extends Ordered[A]
奇怪的类型!但可以看到怎样对 A 实现了Ordered参数化,它本身就是 Container[A]
所以,现在
class MyContainer extends Container[MyContainer] {
def compare(that: MyContainer) = 0
}
他们是有序的了:
scala> List(new MyContainer, new MyContainer, new MyContainer)
res3: List[MyContainer] = List(MyContainer@30f02a6d, MyContainer@67717334, MyContainer@49428ffa) scala> List(new MyContainer, new MyContainer, new MyContainer).min
res4: MyContainer = MyContainer@33dfeb30
鉴于他们都是 Container[_] 的子类型,我们可以定义另一个子类并创建 Container[_] 的一个混合列表:
scala> class YourContainer extends Container[YourContainer] { def compare(that: YourContainer) = 0 }
defined class YourContainer
scala> List(new MyContainer, new MyContainer, new MyContainer, new YourContainer)
res2: List[Container[_ >: YourContainer with MyContainer <: Container[_ >: YourContainer with MyContainer <: ScalaObject]]]
= List(MyContainer@3be5d207, MyContainer@6d3fe849, MyContainer@7eab48a7, YourContainer@1f2f0ce9)
注意结果类型是怎样成为 YourContainer 和 MyContainer 类型确定的下界。这是类型推断的工作。有趣的是,这种类型甚至不需要是有意义的,它只是提供了一个合乎逻辑的最大下界为列表的统一类型。如果现在我们尝试使用 Ordered 会发生什么?
List(new MyContainer, new MyContainer, new MyContainer, new YourContainer).min
<console>:9: error: could not find implicit value for parameter cmp:
Ordering[Container[_ >: YourContainer with MyContainer <: Container[_ >: YourContainer with MyContainer <: ScalaObject]]]
对统一的类型 Ordered[]不存在了。太糟糕了。
结构类型
Scala 支持 结构类型 structural types — 类型需求由接口 构造 表示,而不是由具体的类型表示。
scala> def foo(x: { def get: Int }) = 123 + x.get
foo: (x: AnyRef{def get: Int})Int
scala> foo(new { def get = 10 })
res0: Int = 133
这可能在很多场景都是相当不错的,但这个实现中使用了反射,所以要注意性能!
抽象类型成员
在特质中,你可以让类型成员保持抽象。
scala> trait Foo { type A; val x: A; def getX: A = x }
defined trait Foo
scala> (new Foo { type A = Int; val x = 123 }).getX
res3: Int = 123
scala> (new Foo { type A = String; val x = "hey" }).getX
res4: java.lang.String = hey
在做依赖注入等情况下,这往往是一个有用的技巧。
您可以使用hash操作符来引用一个抽象类型的变量:
scala> trait Foo[M[_]] { type t[A] = M[A] }
defined trait Foo
scala> val x: Foo[List]#t[Int] = List(1)
x: List[Int] = List(1)
类型擦除和清单
正如我们所知道的,类型信息在编译的时候会因为 擦除 而丢失。 Scala的 清单(Manifests) 功能,使我们能够选择性地恢复类型信息。清单提供了一个隐含值,根据需要由编译器生成。
scala> class MakeFoo[A](implicit manifest: Manifest[A]) { def make: A = manifest.erasure.newInstance.asInstanceOf[A] }
scala> (new MakeFoo[String]).make
res10: String = ""
案例分析: Finagle
参见: https://github.com/twitter/finagle
trait Service[-Req, +Rep] extends (Req => Future[Rep]) trait Filter[-ReqIn, +RepOut, +ReqOut, -RepIn]
extends ((ReqIn, Service[ReqOut, RepIn]) => Future[RepOut])
{
def andThen[Req2, Rep2](next: Filter[ReqOut, RepIn, Req2, Rep2]) =
new Filter[ReqIn, RepOut, Req2, Rep2] {
def apply(request: ReqIn, service: Service[Req2, Rep2]) = {
Filter.this.apply(request, new Service[ReqOut, RepIn] {
def apply(request: ReqOut): Future[RepIn] = next(request, service)
override def release() = service.release()
override def isAvailable = service.isAvailable
})
}
} def andThen(service: Service[ReqOut, RepIn]) = new Service[ReqIn, RepOut] {
private[this] val refcounted = new RefcountedService(service) def apply(request: ReqIn) = Filter.this.apply(request, refcounted)
override def release() = refcounted.release()
override def isAvailable = refcounted.isAvailable
}
}
一个服务可以通过过滤器对请求进行身份验证。
trait RequestWithCredentials extends Request {
def credentials: Credentials
}
class CredentialsFilter(credentialsParser: CredentialsParser)
extends Filter[Request, Response, RequestWithCredentials, Response]
{
def apply(request: Request, service: Service[RequestWithCredentials, Response]): Future[Response] = {
val requestWithCredentials = new RequestWrapper with RequestWithCredentials {
val underlying = request
val credentials = credentialsParser(request) getOrElse NullCredentials
}
service(requestWithCredentials)
}
}
注意底层服务是如何需要对请求进行身份验证的,而且还是静态验证。因此,过滤器可以被看作是服务转换器。
许多过滤器可以被组合在一起:
val upFilter =
logTransaction andThen
handleExceptions andThen
extractCredentials andThen
homeUser andThen
authenticate andThen
route
===================================================================================================================================================================
scala 基础语法的更多相关文章
- 【Scala学习之一】 Scala基础语法
环境 虚拟机:VMware 10 Linux版本:CentOS-6.5-x86_64 客户端:Xshell4 FTP:Xftp4 jdk1.8 scala-2.10.4(依赖jdk1.8) spark ...
- 1.scala基础语法总结
Scala基础语法总结:Scala 与 Java 的最大区别是:Scala 语句末尾的分号 ; 是可选的.如果一行里写多个语句那么分号是需要的 val s = "菜鸟教程"; pr ...
- Scala基础语法 (一)
如果你之前是一名 Java 程序员,并了解 Java 语言的基础知识,那么你能很快学会 Scala 的基础语法. Scala 与 Java 的最大区别是:Scala 语句末尾的分号 ; 是可选的. 我 ...
- Scala系统学习(三):Scala基础语法
如果您熟悉Java语言语法和编程,那么学习Scala将会很容易.Scala和Java之间最大的句法差异在于行结束字符的分号(;) 是可选的. 当编写Scala程序时,它可以被定义为通过调用彼此的方法进 ...
- Spark记录-Scala基础语法
如果您熟悉Java语言语法和编程,那么学习Scala将会很容易.Scala和Java之间最大的句法差异在于行结束字符的分号(;) 是可选的. 当编写Scala程序时,它可以被定义为通过调用彼此的方法进 ...
- 第1节 Scala基础语法:5、6、7、8、基础-申明变量和常用类型,表达式,循环,定义方法和函数
4. Scala基础 4.1. 声明变量 package cn.itcast.scala object VariableDemo { def main(args: Array[Strin ...
- scala函数式编程(二) scala基础语法介绍
上次我们介绍了函数式编程的好处,并使用scala写了一个小小的例子帮助大家理解,从这里开始我将真正开始介绍scala编程的一些内容. 这里会先重点介绍scala的一些语法.当然,这里是假设你有一些ja ...
- Scala基础语法学习(一)
1. val和var的区别 val定义的是一个常量,无法改变其内容 scala> val s = 0 s: Int = 0 scala> s = 2 <console>:12: ...
- 第1节 Scala基础语法:13、list集合的定义和操作;16、set集合;17、map集合
list.+:5 , list.::5: 在list集合头部添加单个元素5 : li1.:+(5):在list集合尾部添加单个元素5: li1++li2,li1:::li2:在li1集合尾部添加il2 ...
随机推荐
- android studio一直卡在Gradle:Executing tasks
http://www.eoeandroid.com/forum.php?mod=viewthread&tid=554227 新建了个hello world项目,运行就卡在Gradle:Exec ...
- Safari支不支持HTML5录音? 现在浏览器中最好的解决方案是WebRTC下的 navigator.getUserMedia API。
先放结论:Safari支不支持HTML5录音? ——据我调查,不支持. 现在浏览器中最好的解决方案是WebRTC下的 navigator.getUserMedia API. 可是当使用Can I us ...
- Android组件化方案
Android组件化项目地址:Android组件化项目AndroidModulePattern Android组件化之终极方案地址:http://blog.csdn.net/guiying712/ar ...
- fabric-ca-client
fabric-ca-client enroll -u http://admin:adminpw@localhost:7054 /root/.fabric-ca-client:总用量 12-rwxr-x ...
- float浮点数的四舍五入
瑞生网http://www.rationmcu.com版权所有 前几天,有个小伙伴在做实验过程中,发现了一个奇怪的现象,这个现象就是… 他在用printf输出浮点数的时候,想把数据保留到小数点后的两位 ...
- 存储过程参数CHAR传过来null导致超时.
调用的时候不要传NULL,可以传 '' ALTER PROCEDURE [dbo].[up_UC_GetUCExecuteEPList] @Code VARCHAR(3) ,--ch ...
- 洛谷 P1138 第k小整数
题目描述 现有n个正整数,n≤10000,要求出这n个正整数中的第k个最小整数(相同大小的整数只计算一次),k≤1000,正整数均小于30000. 输入输出格式 输入格式: 第一行为n和k; 第二行开 ...
- Java SPI(Service Provider Interface)简介
SPI 简介 SPI 全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制. 一个服务(Service)通常指的是已知的接口或者抽象类,服务提供方就是对这 ...
- Populating Next Right Pointers in Each Node II leetcode java
题目: Follow up for problem "Populating Next Right Pointers in Each Node". What if the given ...
- mybatis @Select注解中如何拼写动态sql
@Mapper public interface DemandCommentMapper extends BaseMapper<DemandComment>{ @Select(" ...