这篇文章用来总结 Scala 学习中需要记录的知识,也会是以后 Scala 相关知识的索引。
Assignment
Scala 的赋值语句的返回值是 Unit, 因此不能使用 x=y=1 类似的赋值语法。
可以使用 `@`` 的小技巧来完成一个连续赋值的语法糖。
y = 1 // Unit ()// not workx = y = 1// trickvar x@y = 1 // x = 1, y = 1
Input
读取 Console 数据。
import scala.io._val name = StdIn.readLine("your name:")print("your age:")val age = StdIn.readInt()
Output
格式化输出,建议使用 f插值表达式,类型会在编译期得到检查。
printf("%d year %f seconds", year, sec)// recommend, type-safeprint(f"$year ${sec}%7.2f seconds")// raw textprint(raw"\n 123")
Loops
Scala 没有 break, continue 关键字,只能通过其他方式来实现。
// 1. use Booleanvar flag = truefor (i <- 1 to 9) { if (flag) { print(i) flag = false } else flag = true}// 2. use `return`def loop(): Int = { for (i <- 1 to 10) { if (i == 2) return -1 else () } 0}// 3. use `break` method in the `Breaks` object// not recommend// the control transfer is done by throwing and catching an exception,// so you should avoid this mechanism when time is of essence.import scala.util.control.Breaks._def loop1(): Unit = { breakable { for (i <- 1 to 10) { if (i == 3) break else println(i) } }}
Scala 的循环语句中,本地的变量名可以覆盖使用。
val n = 10// local n will be overlappingfor (n <- 1 to 9) { print(n) // print 1-9}
Advanced for Loops
Scala 有更加便利的循环操作,可以完成多级循环以及列表推导。
非常简单的语法糖完成多级的 for 循环
// multiple generatorsfor (i <- 1 to 3; j <- 1 to 3) println(f"${i*10 + j}%3d")// guardfor (i <- 1 to 3; j <- 1 to 3 if i!=j) println(f"${i*10 + j}%3d")// definitions, any number.for (i <- 1 to 3; from = 4-i; j <- from to 3) println(f"${i*10 + j}%3d")
列表推导
列表推导生成的结果总是兼容第一个生成器的格式,可以看2、3例,第一个生成器是 String, 生成的就是 String格式。
for (i <- 1 to 9) yield i%5 // Yields Vector(1, 2, 3, 4, 0, 1, 2, 3, 4)// The generated collection is compatible with the first generator.for (c <- "Hello"; i <- 0 to 1) yield (c + i).toChar // Yields "HIeflmlmop"for (i <- 0 to 1; c <- "Hello") yield (c + i).toChar // Yields Vector('H', 'e', 'l', 'l', 'o', 'I', 'f', 'm', 'm', 'p')
如果不想使用分号的风格 ,可以使用 {} 加换行 替代
for { i <- 1 to 3 from = 4 - i j <- from to 3 }
Variable Arguments
这是普通的可变长参数函数的实现,这里主要是指出一下 Scala 特有的语法。
能够使一个列表转变成可变参数的形式传递到方法内。
def sum(args: Int *): Int = { if (args.isEmpty) 0 else args.head + sum(args.tail :_*)}sum(1 to 5 :_*)
一道 String Interpolator 的题目
快捷的定义一个 java.time.LocalDate,使用到了 implicit 关键字。
import java.time.LocalDateimplicit class DateInterpolator(val sc: StringContext) extends AnyVal { def date(args: Any*): LocalDate = { if (args.length != 3) throw new IllegalArgumentException("arguments should contain year, month, day.") for (x <- sc.parts) if (x.length > 0 && !x.equals("-")) throw new IllegalArgumentException("year-month-day format required") LocalDate.of(args(0).toString.toInt, args(1).toString.toInt, args(2).toString.toInt) }}val year = 2017val month = 1val day = 5date"$year-$month-$day" // java.time.LocalDate = 2017-01-05
Array
Scala 中的数组操作, Array 对应的是定长数组,ArrayBuffer 对应的是 Java的 ArrayList。
// Traverse indicesfor (i <- 0 until a.length) { }// orfor (i <- a.indices) { }// To visit every second elementfor (i <- 0 until a.length by 2) { }// To visit the elements starting from the end of the arrayfor (i <- 0 until a.length by -1) { }// orfor (i <- a.indices.reverse) { }// Traverse all values of the listfor (i <- a) { }
Class
Scala 实现 class的方式不同于 Java。Scala 对所有的 var,val都会选择性地生成对应的 Setter & Getter。
| generate | var | val |
|---|
| setter | √ | × |
| getter | √ | √ |
如果声明是 private 的话,那么生成的 Setter & Getter 也是 private 的。
如果不想要生成 Setter & Getter,可以使用 private[this] 来修饰字段。
这里还有一个特殊点:字段声明是 private 的,只有该类的对象才能访问,这点和 Java的表现不同(Java 是只能在类部才能使用)。
下面代码中的 other也是一个 Counter类型,他也能访问 private var value。如果使用了 private[this],表现就和 Java中一样了。
class Counter { private var value = 0 def increment() { value += 1 } def isLess(other : Counter) = value < other.value // Can access private field of other object}
Extractors with No Arguments
Extractors 可以用无参形式调用,这种情况下,它的返回值应该是一个 Boolean。
下面是一个样例,可以看到无参形式的 Extractors在模式匹配的时候使用。
object Name { def unapply(input: String) = { val pos = input.indexOf(" ") if (pos == -1) None else Some((input.substring(0, pos), input.substring(pos + 1))) }}object IsCompound { def unapply(input: String) = input.contains(" ")}val author = "king kong W" // "king kongW"author match { case Name(first, IsCompound()) => print(first + " mix " ) // 当 IsCompound() 的返回值为 True时执行 case Name(first, last) => print(first + " : " + last)}
Functions as Values
Scala 中函数(Function)也是第一等公民,可以作为值来传递。但是方法(Method)并不是函数,无法作为值传递。
下面展示一下方法如何转化为一个函数。
PS: 任何时候使用 def 关键词定义的都是方法,不是函数。
import scala.math._// -- method from package object --val fun = ceil _ // the _ turns the ceil method into a function.val func:(Double) => Double = ceil // The _ suffix is not necessary when you use a method name in a context where // a function is expected.// -- method from a class --val f = (_: String).charAt(_:Int)val fc: (String, Int) => Char = _.charAt(_)
Control Abstractions
Scala 中有两种调用形式的参数, call-by-name和 call-by-value,大多数情况下只使用后者,现在有一种使用前者的情况。
// call-by-valuedef runInThread(block: () => Unit) { // 这是对一个参数的类型定义 new Thread { override def run() { block() } // 这里是调用函数 }.start()}runInThread { () => println("Hi"); Thread.sleep(10000); println("Bye") } // 这里调用的时候 必须是 `() =>`带这个开头,就显得很多余
// call-by-namedef runInThread(block: => Unit) { new Thread { override def run() { block } }.start()}runInThread { println("Hi"); Thread.sleep(10000); println("Bye") } // 这里就可以省略掉 `() =>`这个开头了,匿名函数写起来就很简洁
可以看到 call-by-name 的参数调用使得方法在调用的时候非常方便,这里利用这一点实现类似 while 的语法。
// definition // call-by-name // call-by-namedef until(condition: => Boolean)(block: => Unit) { if (!condition) { block until(condition)(block) }}// -- sample --var x = 10until (x == 0) { // without `()=>`, pretty concise x -= 1 println(x)}Unlike a regular (or call-by-value) parameter, the parameter expression is not evaluated when the function is called.After all, we don’t want x == 0 to evaluate to false in the call to until.
这里说的非常重要,正是因为 call-by-name 的这个特性,才使得 until 方法可以对在运行时求值,而不是调用方法时 x==0 就已经作为值 false 传入了。
Patterns in Variable Declarations
Scala 支持在变量声明时的解构操作,如下操作:
val (x, y) = (1, 2)
对于表达式 val p(x1, ..., xn) = e, 定义上等同与
val $result = e match { case p(x1, ..., xn) => (x1, ..., xn) }val x1 = $result._1...val xn = $result._n
其中 x1~xn 是 free variables,可以是任意的值,如下表达式,在 Scala中是合理的:
val 2 = x
等同于:
var $result = x match { case 2 => () }// No assignments.
并没有赋值语句。 这等同于:
if (!(2 == x)) throw new MatchError
Partial Functions
Scala 又一迷之特性,这个语法糖不知道又会有多少玩法了。 偏函数,它的定义是这样的:
a function which may not be defined for all inputs. PartialFunction[A, B]. (A is the parameter type, B the return type.)
实际上如果一个偏函数穷举了所有可能性,那他就变成了一个 Function1。一个神奇的方法… Scala 设置了 Function1 到 Function22 总共可以允许 22 个参数。
然后就是神奇的语法糖了,甜不甜…
A Seq[A] is a PartialFunction[Int, A], and a Map[K, V] is a PartialFunction[K, V].
基于这个可以带来的操作:
val names = Array("Alice", "Bob", "Carmen")val scores = Map("Alice" -> 10, "Carmen" -> 7)names.collect(scores) // Yields Array(10, 7)
偏函数有 lift 函数,可以将偏函数转变为一个正常的函数,返回值是 Option[T]。反之也可以将一个有 Option[T] 返回值的函数,通过 unlift 转变为一个偏函数。
try 语句的 catch 子句就是一个偏函数,可以将这个字句赋值给一个变量。
// call by namedef tryCatch[T](b: => T, catcher: PartialFunction[Throwable, T]) = try { b } catch catcherval result = tryCatch(str.toInt, { case _: NumberFormatException => -1 })
可以看到 catch 子句就是一个偏函数, 通过 catcher 这个变量可以动态的切换偏函数。
不得不感叹一声,这个设计思维啊。