高阶函数

什么是高阶函数?

在scala中,函数可以作为参数来传递。下面的代码定义一个接受函数作为参数的函数:

// 函数定义
def operation(func:(Int, Int) => Int) = {
    val result = func(4,4)
    println(result)
}

// 接下来,定义一个与预期签名匹配的函数:
val add = (x: Int, y:Int) => { x + y }

// 将add函数作为参数传递给operation函数
operation(add)

// 以上可以简写,相当于 val func = (x: Int, y:Int) => { x + y }
operation((x: Int, y:Int) => { x + y })

能接收函数参数的函数,或者返回值为函数的函数,称为"高阶函数",比如这里的operation就是一个高阶函数。

下面定义一个返回一个函数的高阶函数:

def greeting() = {
    println("any any ...")
    (name: String) => {"hello" + " " + name}   // 最后一行,是函数的返回值
}

// 调用函数greeting,得到它的返回值,这个返回值本身又是一个函数: 
// 相当于 val func = (name: String) => {"hello" + " " + name}
val func = greeting()

// 调用func
func("朋友")

在这里,greeting函数就是一个“高阶函数”,因为它返回了一个函数。所以可以这样调用:

greeting()("朋友")

下面是另一个使用高阶函数的示例:

def functionOps3: Unit = {

    // 1.匿名函数作为参数
    def highOrderFunc(name:String, func:(String) => Unit): Unit = {
      func(name)
    }

    highOrderFunc("xpleaf", (name:String) => println("Hello, " + name))

    // 2.将匿名函数作为返回值传递给另外一个函数
    def getGoodBayFunction(gMsg: String) = (gName: String) => println(gMsg + ", " + gName)

    val goodbayFunction = getGoodBayFunction("good bye")
    goodbayFunction("xpleaf")

}

functionOps3

call-by-name与call-by-value

本节是前一节高阶函数内容的延续,我们将展示按call-by-name和call-by-value函数参数之间的区别。

在下面的例子中,我们首先定义一个包含Tuple3元素的List,代表一组订单。我们定义一个函数循环遍历列表中的每个Tuple3元素来计算总成本。

假设商品在世界各地销售,因此需要将购买订单的总成本转换为使用的当地货币。我们分别定义了call-by-value函数参数调用和call-by-name函数参数的调用。请注意其中的区别。

import scala.util.Random

object demo {

  // 定义一个placeOrder()函数,它将获取订单列表。
  // 但是该函数还有一个exchangeRate参数,用于将总成本转换为当地货币。
  // call-by-value
  // 这意味着任何传递的汇率exchangeRate只会被计算一次
  def placeOrder(orders: List[(String, Int, Double)])(exchangeRate: Double): Double = {
    var totalCost: Double = 0.0
    orders.foreach {order =>
      val costOfItem = order._2 * order._3 * exchangeRate
      println(s"Cost of ${order._2} ${order._1} = £$costOfItem")
      totalCost += costOfItem
    }
    totalCost
  }

  // call-by-name
  // 调用名称函数参数exchangeRate: => Double将在每次调用任何exchangeRate函数时计算它
  def placeOrderWithByNameParameter(orders:List[(String, Int, Double)])(exchangeRate:=> Double):Double = {
    var totalCost: Double = 0.0
    orders.foreach {order =>
      val costOfItem = order._2 * order._3 * exchangeRate
      println(s"Cost of ${order._2} ${order._1} = £$costOfItem")
      totalCost += costOfItem
    }
    totalCost
  }

  def main(args: Array[String]): Unit = {
    val listOrders = List(("苹果", 5, 2.50), ("香蕉", 10, 3.50))
    println(s"订单总费用 = £${placeOrder(listOrders)(0.5)}")
    println(s"订单总费用 = £${placeOrderWithByNameParameter(listOrders)(0.5)}")

    // 创建一个函数,它将随机生成美元USD到英镑GBP的货币转换
    val randomExchangeRate = new Random(10)
    def usdToGbp: Double = {
      val rate = randomExchangeRate.nextDouble()
      println(s"取美元对英镑的汇率 = $rate")
      rate
    }

    // 调用call-by-name参数的函数
    println(s"订单总费用 = £${placeOrderWithByNameParameter(listOrders)(usdToGbp)}")
    // 对于列表中的每个订单,都将创建一个新的汇率,这是因为每次都在计算call-by-name函数usdToGbp函数
  }
}

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

Cost of 5 苹果 = £6.25
Cost of 10 香蕉 = £17.5
订单总费用 = £23.75
Cost of 5 苹果 = £6.25
Cost of 10 香蕉 = £17.5
订单总费用 = £23.75
取美元对英镑的汇率 = 0.7304302967434272
Cost of 5 苹果 = £9.13037870929284
取美元对英镑的汇率 = 0.2578027905957804
Cost of 10 香蕉 = £9.023097670852312
订单总费用 = £18.15347638014515

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