类型化函数,多态函数和符号函数

类型化函数

所谓类型化函数,指的是在调用函数时可以指定参数的类型。Scala的类型化函数通过变量的使用提供了更大的灵活性。那么,如何定义一个泛型类型函数将指定其参数的类型?

下面我们将创建一个类型化函数,它将指定一个T类型的泛型参数,如下所示:

object demo {

  // 应用折扣的泛型函数
  def applyDiscount[T](discount: T) {
    discount match {
      case d: String =>
        println(s"在数据库中查找 $d 的百分比折扣")

      case d: Double =>
        println(s"$d 折扣优惠")

      case _ =>
        println("不支持的折扣类型")
    }
  }

  def main(args: Array[String]): Unit = {
    // 要调用类型化函数applyDiscount(),必须提供其参数的类型。
    applyDiscount[String]("COUPON_123")
    applyDiscount[Double](10)
    applyDiscount[Int](10)
  }
}

执行上面的代码,输出内容如下:

在数据库中查找 COUPON_123 的百分比折扣
10.0 折扣优惠
不支持的折扣类型

虽然可以依赖Scala的类型推断来推断函数参数的类型,但一般来说,显式指定参数类型是一个很好的实践。

多态函数

多态函数指的是具有泛型返回类型的函数。在多态函数中,我们可以指定参数的类型以及函数的返回类型。

下面让我们定义另一个本质上是多态的函数。我们将函数命名为applyDiscountWithReturnType(),它将接受泛型类型T作为其折扣参数。但是它也会返回一个类型为T的Sequence。

object demo {

  // 定义一个多态函数
  // 它将接受泛型类型T作为其折扣参数。但是它也会返回一个类型为T的Sequence。
  def applyDiscountWithReturnType[T](discount: T): Seq[T] = {
    discount match {
      case d: String =>
        println(s"在数据库中查找 $d 的百分比折扣")
        Seq[T](discount)

      case d: Double =>
        println(s"$d 折扣优惠")
        Seq[T](discount)

      case d @ _ =>
        println("不支持的折扣类型")
        Seq[T](discount)
    }
  }

  def main(args: Array[String]): Unit = {
    // 使用不同的类型,即String、Double和Char来分别调用多态函数
    println(s"带String参数调用结果 = ${applyDiscountWithReturnType[String]("COUPON_123")}")

    println()
    println(s"带Double参数调用结果 = ${applyDiscountWithReturnType[Double](10.5)}")

    println()
    println(s"带Char参数调用结果 = ${applyDiscountWithReturnType[Char]('U')}")
  }
}

执行以上代码,输出结果如下:

在数据库中查找 COUPON_123 的百分比折扣
带String参数调用结果 = List(COUPON_123)

10.5 折扣优惠
带Double参数调用结果 = List(10.5)

不支持的折扣类型
带Char参数调用结果 = List(U)

为简单起见,我们将为模式匹配中的每一种情况返回一个类型为T的新序列。对于调用多态函数applyDiscountWithReturnType()时使用的每个指定类型,该函数还返回相同类型的Sequence序列。

关于@运算符

@运算符允许将匹配的模式绑定到变量。请看下面的示例:

object demo {

  def main(args: Array[String]): Unit = {

    // 例如,考虑以下情况:
    val o: Option[Int] = Some(2)

    // 可以很容易地提取内容:
    o match {
      case Some(x) => println(x)
      case None =>
    }

    // 但是,如果想要的不是Some的内容,而是Option本身呢?
    // 这将通过以下方式实现:
    o match {
      case x @ Some(_) => println(x)
      case None =>
    }
    
  }

}

执行上面的代码,输出结果如下:

2
Some(2)

注意,@ 可用于任何级别,而不仅仅是顶级的匹配。

符号函数

符号函数指的是仅使用符号而不是字母命名的函数。当必须创建特定于域的语言语法时,Scala的这个特性是一个很好的构建块。

定义名称为符号的函数本质上与定义任何其他函数相同。这个函数没有名称,而是用一些符号来定义。例如:

def -(discount: Double): Double = {
  totalCost - discount
}

调用一个函数,其名称只是一个符号,就像上面步骤中的-函数一样,与调用任何其他函数没有什么不同。

println(s"调用函数 - = ${donutCostCalculator.-(10.5)}")

Scala允许使用如下所示的操作符样式来调用函数:

println(s"调用函数-使用操作符样式表示法 = ${donutCostCalculator - 10.5}")

使用操作符样式调用函数与调用函数没有太大区别,只是不需要指定点(.)。当调用函数名只是符号时,使用操作符风格更清晰。

既然已经知道了如何定义名称只是符号的函数,那么让我们创建另一个名为+++的函数。

def +++(taxAmount: Double): Double = {
  totalCost + taxAmount
}

完整代码如下:

object demo {

  val totalCost = 100

  // 定义名称为符号的函数本质上与定义任何其他函数相同。这个函数没有名称,而是用一些符号来定义。例如:
  def -(discount: Double): Double = {
    totalCost - discount
  }

  // 既然已经知道了如何定义名称只是符号的函数,那么创建另一个名为+++的函数。
  def +++(taxAmount: Double): Double = {
    totalCost + taxAmount
  }

  def main(args: Array[String]): Unit = {
    // 调用一个函数,其名称只是一个符号,就像上面步骤中的-函数一样,与调用任何其他函数没有什么不同。
    println(s"调用函数 - = ${demo07.-(10.5)}")

    // Scala允许使用如下所示的操作符样式来调用函数
    // 使用操作符样式调用函数与调用函数没有太大区别,只是不需要指定点(.)。
    // 当调用函数名只是符号时,使用操作符风格更清晰。
    println(s"调用函数-使用操作符样式表示法 = ${demo07 - 10.5}")

    // 加税计算的方法
    println(s"调用+++函数 = ${demo07 +++ 13.5}")
  }
}

执行以上代码,输出结果如下:

调用函数 - = 89.5
调用函数-使用操作符样式表示法 = 89.5
调用+++函数 = 113.5

《Spark原理深入与编程实战》