你可以使用下划线“_”部分应用一个函数,结果将得到另一个函数。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>
这是一个特殊的语法,可以向方法传入任意多个同类型的参数。例如要在多个字符串上执行String的capitalize
函数,可以这样写:
def capitalizeAll(args: String*) = { args.map { arg => arg.capitalize } } scala> capitalizeAll("rarity", "applejack") res2: Seq[String] = ArrayBuffer(Rarity, Applejack)
特质
是一些字段和行为的集合,可以扩展或混入(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编程: 用特质,还是不用特质?
单例对象用于持有一个类的唯一实例。通常用于工厂模式。
object Timer { var count = 0 def currentCount(): Long = { count += 1 count } }
可以这样使用:
scala> Timer.currentCount() res0: Long = 1
单例对象可以和类具有相同的名称,此时该对象也被称为“伴生对象”。我们通常将伴生对象作为工厂使用。
下面是一个简单的例子,可以不需要使用’new’来创建一个实例了。
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特质集合下标从1开始一直到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 }
在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特质集合下标从1开始一直到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提供了一些应对这种情况的有效工具。
使用样本类可以方便得存储和匹配类的内容。你不用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块将被调用;它不是表达式的一部分。