【Scala之旅】参数与操作符

本节翻译自

综述:本节介绍了函数中默认参数和带名参数;操作符以及标识符的优先级,还有可以从输入中提取元组或值的序列的提取器。

默认参数

Scala提供了给参数默认值的能力,可以让调用者忽略这些参数。

def log(message: String, level: String = "INFO") = println(s"$level: $message")

log("System starting")  // prints INFO: System starting
log("User not found", "WARNING")  // prints WARNING: User not found

参数 level 有一个默认值,所以它是可选的。在最后一行,参数 "WARNING" 覆盖默认参数 "INFO"。你可能在Java中使用重载方法,可以使用带有可选参数的方法来实现相同的效果。但是,如果调用者省略了一个参数,那么必须指定任何下列参数。

class Point(val x: Double = 0, val y: Double = 0)
val point1 = new Point(y = 1)

这里我们必须要指明 y = 1

注意,当从 Java 代码调用时,Scala中的默认参数不是可选的:

// Point.scala
class Point(val x: Double = 0, val y: Double = 0)
// Main.java
public class Main {
    public static void main(String[] args) {
        Point point = new Point(1);  // does not compile
    }
}

带名参数

在调用方法时,您可以使用参数名称来标记参数:

  def printName(first: String, last: String): Unit = {
    println(first + " " + last)
  }

  printName("John", "Smith")  // Prints "John Smith"
  printName(first = "John", last = "Smith")  // Prints "John Smith"
  printName(last = "Smith", first = "John")  // Prints "John Smith"

注意命名参数的顺序可以重新排列。但是,如果有一些参数被命名,而另一些参数没有,则未命名的参数必须以方法签名中参数的顺序排在第一位。

def printName(first: String, last: String): Unit = {
  println(first + " " + last)
}

printName(last = "Smith", "john")  // Does not compile

请注意,带名参数与对Java方法的调用不兼容。

操作符

在 Scala 中,操作符是一个方法。任何带有单个参数的方法都可以使用中缀操作符。例如,+ 可以用点符号来表示:

10.+(1)

然而,作为一个中缀操作符更容易阅读:

10 + 1

定义和使用操作符

你可以使用任何合法的标识符作为操作符。这包括 add 或像 + 符号之类的名称。

case class Vec(val x: Double, val y: Double) {
  def +(that: Vec) = new Vec(this.x + that.x, this.y + that.y)
}

val vector1 = Vec(1.0, 1.0)
val vector2 = Vec(2.0, 2.0)

val vector3 = vector1 + vector2
vector3.x  // 3.0
vector3.y  // 3.0

Vec 有一个方法 +,我们使用它来把 vector1vector2 加起来。使用圆括号的话,你就可以使用可读的语法构建复杂的表达式。下面是类 MyBool 的定义,它包括了 andor 方法:

case class MyBool(x: Boolean) {
  def and(that: MyBool): MyBool = if (x) that else this
  def or(that: MyBool): MyBool = if (x) this else that
  def negate: MyBool = MyBool(!x)
}

现在可以使用将 andor 方法作为中缀操作符来使用了:

def not(x: MyBool) = x.negate
def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y)

这有助于使 xor 的定义更加可读。

优先级

当一个表达式使用多个操作符时,操作符会根据第一个字符的优先级进行计算。

(characters not shown below)
* / %
+ -
:
= !
< >
&
^
|
(all letters)

这适用于你定义的函数。例如,下面的表达式:

a + b ^? c ?^ d less a ==> b | c

相似于

((a + b) ^? (c ?^ d)) less ((a ==> b) | c)

?^ 的优先级最高,因为它以字符 ? 开头。+ 具有第二优先级,其次是 ^?==>|less

提取器对象

提取器对象是具有一个 unapply 方法的对象。与其相反的是 apply 方法就像一个构造函数,它接受参数并创建一个对象,而 unapply 则获取一个对象并试图返回参数。这通常用于模式匹配和部分应用函数中。

import scala.util.Random

object CustomerID {

  def apply(name: String) = s"$name--${Random.nextLong}"

  def unapply(customerID: String): Option[String] = {
    val name = customerID.split("--").head
    if (name.nonEmpty) Some(name) else None
  }
}

val customer1ID = CustomerID("Sukyoung")  // Sukyoung--23098234908
customer1ID match {
  case CustomerID(name) => println(name)  // prints Sukyoung
  case _ => println("Could not extract a CustomerID")
}

apply 方法从一个 name 创建一个 CustomerID 字符串。而 unapply 反过来将 name 提取出来。我们调用的 CustomerID("Sukyoung"),其实是 CustomerID.apply("Sukyoung") 的简写语法。当我们调用 case CustomerID(name) => customer1ID 时,实际上我们使用的是 unapply 方法。

unapply 方法也可以用来赋值。

val customer2ID = CustomerID("Nico")
val CustomerID(name) = customer2ID
println(name)  // prints Nico

这相当于 val name = CustomerID.unapply(customer2ID).get,如果没有使用匹配,会抛出 scala.MatchError

val CustomerID(name2) = "--asdfasdfasdf"

unapply 的返回类型应该选择如下几种:

  • 如果只是一个测试,返回一个布尔值。例如 case even()
  • 如果它返回T类型的一个子值,返回一个 Option[T]
  • 如果你想返回几个子值 T1,...,Tn ,将它们分组为一个可选的元组Option[(T1,...,Tn)]

有时,子值的数量不是固定的,我们希望返回一个序列。出于这个原因,你还可以通过 unapplySeq 定义模式,该机制返回 Option[Seq[T]],该机制用于模式 case List(x1, ..., xn)

    原文作者:gcusky
    原文地址: https://segmentfault.com/a/1190000014104419
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞