【Scala之旅】高阶函数
类型推断Scala 编译器通常可以推断出表达式的类型,因此你不必显式声明它。 省略类型val businessName = "Montreux Jazz Café" 编译器可以检测到 def squareOf(x: Int) = x * x 编译器可以推断返回类型是一个 对于递归方法,编译器不能推断出结果类型。由于这个原因,下面的程序会使编译器失败: def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) 当多态方法被调用或者泛型类被实例化时,也不用强制地指定类型参数。Scala 编译器会从上下文和实际方法/构造函数参数的类型中推断出这些缺失的类型参数。 这里有两个例子: case class MyPair[A,B](x: A,y: B); val p = MyPair(1,"scala") // type: MyPair[Int,String] def id[T](x: T) = x val q = id(1) // type: Int 编译器使用 参数编译器从不推断方法参数类型。但是,在某些情况下,它可以在函数作为参数传递时推断匿名函数参数类型。 Seq(1,3,4).map(x => x * 2) // List(2,6,8) map 的参数是 何时不依靠类型推断通常认为在公共 API 中声明成员的类型更具可读性。因此,我们建议你对任何将暴露给用户的代码 API 进行明确的类型标记。 此外,类型推断有时可能推断出一个太特定的类型。 假设我们写: var obj = null 我们现在不能继续进行重新分配: obj = new AnyRef 它不会编译,因为 高阶函数Scala 允许对高阶函数的定义。这些函数将其他函数作为参数,或者其结果是一个函数。这是可能的,因为函数在 Scala 中是一等公民。在这一点上,术语可能会有些混乱,我们使用短语“高阶函数”来表示++将函数作为参数++或++返回函数++的方法和函数。 最常见的例子之一是可用于Scala中的集合的高阶函数 val salaries = Seq(20000,70000,40000) val doubleSalary = (x: Int) => x * 2 val newSalaries = salaries.map(doubleSalary) // List(40000,140000,80000)
为了简化代码,我们可以使函数匿名并直接将其作为参数传递给 map: val salaries = Seq(20000,40000) val newSalaries = salaries.map(x => x * 2) // List(40000,80000) 注意 val salaries = Seq(20000,40000) val newSalaries = salaries.map(_ * 2) 由于Scala编译器已经知道参数的类型(一个 Int),因此你只需提供该函数的右侧。唯一需要注意的是,你需要使用_代替参数名称(在前面的例子中是x)。 强迫方法转化为函数也可以将方法作为参数传递给高阶函数,因为 Scala 编译器会将该方法强制为一个函数。 case class WeeklyWeatherForecast(temperatures: Seq[Double]) { private def convertCtoF(temp: Double) = temp * 1.8 + 32 def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- passing the method convertCtoF } 这里 接受函数的函数使用高阶函数的一个原因是减少冗余代码。假设你想要一些可以通过各种因素提高某人薪水的方法。不创建更高阶的函数,它可能看起来像这样: object SalaryRaiser { def smallPromotion(salaries: List[Double]): List[Double] = salaries.map(salary => salary * 1.1) def greatPromotion(salaries: List[Double]): List[Double] = salaries.map(salary => salary * math.log(salary)) def hugePromotion(salaries: List[Double]): List[Double] = salaries.map(salary => salary * salary) } 请注意,三种方法中的每一种方法仅以乘法因数变化。为了简化,您可以将重复的代码提取到更高阶的函数中,如下所示: object SalaryRaiser { private def promotion(salaries: List[Double],promotionFunction: Double => Double): List[Double] = salaries.map(promotionFunction) def smallPromotion(salaries: List[Double]): List[Double] = promotion(salaries,salary => salary * 1.1) def bigPromotion(salaries: List[Double]): List[Double] = promotion(salaries,salary => salary * math.log(salary)) def hugePromotion(salaries: List[Double]): List[Double] = promotion(salaries,salary => salary * salary) } 新方法 返回函数的函数有些情况下你想要生成一个函数。这是一个返回函数的方法的例子。 def urlBuilder(ssl: Boolean,domainName: String): (String,String) => String = { val schema = if (ssl) "https://" else "http://" (endpoint: String,query: String) => s"$schema$domainName/$endpoint?$query" } val domainName = "www.example.com" def getURL = urlBuilder(ssl=true,domainName) val endpoint = "users" val query = "id=1" val url = getURL(endpoint,query) // "https://www.example.com/users?id=1": String 注意 urlBuilder 嵌套方法在Scala中,方法可以嵌套方法。下面的对象提供了计算一个给定数字的阶乘的 def factorial(x: Int): Int = { def fact(x: Int,accumulator: Int): Int = { if (x <= 1) accumulator else fact(x - 1,x * accumulator) } fact(x,1) } println("Factorial of 2: " + factorial(2)) println("Factorial of 3: " + factorial(3)) 这个程序的输出是: Factorial of 2: 2 Factorial of 3: 6 柯里化方法可以定义多个参数列表。当使用较少数量的参数列表调用某个方法时,这将产生一个将缺少的参数列表作为其参数的函数 这种形式上被称为柯里化。 下面是一个例子,定义在 Scala 集合的 def foldLeft[B](z: B)(op: (B,A) => B): B
从初始值 0 开始, val numbers = List(1,2,4,5,7,8,9,10) val res = numbers.foldLeft(0)((m,n) => m + n) print(res) // 55 多个参数列表具有更详细的调用语法;因此应该谨慎使用。建议的使用案例包括: 单函数参数在单个函数参数的情况下,如上面 numbers.foldLeft(0,{(m: Int,n: Int) => m + n}) 请注意,此处使用多个参数列表使我们能够利用 Scala 类型推断来使代码更加简洁,如下所示;这在一个没有柯里化定义的函数中是不可能的。 numbers.foldLeft(0)(_ + _) 另外,它允许我们修改参数 val numbers = List(1,10) val numberFunc = numbers.foldLeft(List[Int]())_ val squares = numberFunc((xs,x) => xs:+ x*x) print(squares.toString()) // List(1,16,25,36,49,64,81,100) val cubes = numberFunc((xs,x) => xs:+ x*x*x) print(cubes.toString()) // List(1,27,125,216,343,512,729,1000) 隐式参数要将参数列表中的某些参数指定为 def execute(arg: Int)(implicit ec: ExecutionContext) = ??? 换名参数换名参数 只在使用时进行求值。它们与换值参数 形成对比。要创建一个换名参数,只需将 def calculate(input: => Int) = input * 37 换名参数具有一个优点:如果不在函数体中使用则不求值的优点。另一方面,换值参数的优点是它们只被求值一次。 下面是我们如何实现 def whileLoop(condition: => Boolean)(body: => Unit): Unit = if (condition) { body whileLoop(condition)(body) } var i = 2 whileLoop (i > 0) { println(i) i -= 1 } // prints 2 1 方法 现在,当我们通过 如果这个参数是计算密集型的,或者需要一个长时间运行的代码块,比如获取URL,那么在使用参数之前,延迟对参数进行求值的能力可以帮助提高性能。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |