Scala 学习总结

这篇文章用来总结 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 对所有的 varval都会选择性地生成对应的 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-namecall-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 这个变量可以动态的切换偏函数。

不得不感叹一声,这个设计思维啊。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 nickchenyx@gmail.com

Title:Scala 学习总结

Count:2.3k

Author:nickChen

Created At:2018-04-17, 16:04:08

Updated At:2023-05-08, 23:27:10

Url:http://nickchenyx.github.io/2018/04/17/scala-for-the-impatient/

Copyright: 'Attribution-non-commercial-shared in the same way 4.0' Reprint please keep the original link and author.