这篇文章用来总结 Scala 学习中需要记录的知识,也会是以后 Scala 相关知识的索引。
Assignment
Scala
的赋值语句的返回值是 Unit
, 因此不能使用 x=y=1
类似的赋值语法。
可以使用 `@`` 的小技巧来完成一个连续赋值的语法糖。
y = 1 // Unit ()
// not work
x = y = 1
// trick
var 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-safe
print(f"$year ${sec}%7.2f seconds")
// raw text
print(raw"\n 123")
Loops
Scala
没有 break
, continue
关键字,只能通过其他方式来实现。
// 1. use Boolean
var flag = true
for (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 overlapping
for (n <- 1 to 9) {
print(n) // print 1-9
}
Advanced for Loops
Scala
有更加便利的循环操作,可以完成多级循环以及列表推导。
非常简单的语法糖完成多级的 for 循环
// multiple generators
for (i <- 1 to 3; j <- 1 to 3) println(f"${i*10 + j}%3d")
// guard
for (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.LocalDate
implicit 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 = 2017
val month = 1
val day = 5
date"$year-$month-$day" // java.time.LocalDate = 2017-01-05
Array
Scala
中的数组操作, Array
对应的是定长数组,ArrayBuffer
对应的是 Java的 ArrayList
。
// Traverse indices
for (i <- 0 until a.length) { }
// or
for (i <- a.indices) { }
// To visit every second element
for (i <- 0 until a.length by 2) { }
// To visit the elements starting from the end of the array
for (i <- 0 until a.length by -1) { }
// or
for (i <- a.indices.reverse) { }
// Traverse all values of the list
for (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-value
def runInThread(block: () => Unit) { // 这是对一个参数的类型定义
new Thread {
override def run() { block() } // 这里是调用函数
}.start()
}
runInThread { () => println("Hi"); Thread.sleep(10000); println("Bye") }
// 这里调用的时候 必须是 `() =>`带这个开头,就显得很多余
// call-by-name
def 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-name
def until(condition: => Boolean)(block: => Unit) {
if (!condition) {
block
until(condition)(block)
}
}
// -- sample --
var x = 10
until (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 name
def tryCatch[T](b: => T, catcher: PartialFunction[Throwable, T]) =
try { b } catch catcher
val result = tryCatch(str.toInt,
{ case _: NumberFormatException => -1 })
可以看到 catch
子句就是一个偏函数, 通过 catcher
这个变量可以动态的切换偏函数。
不得不感叹一声,这个设计思维啊。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 [email protected]