From a5a33b0a417b6f13f39e51bd48202671170122b8 Mon Sep 17 00:00:00 2001 From: realwunan Date: Fri, 12 Oct 2018 16:55:49 +0800 Subject: [PATCH 1/2] initial trnaslate of page higher-order-functions --- _zh-cn/tour/higher-order-functions.md | 97 ++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/_zh-cn/tour/higher-order-functions.md b/_zh-cn/tour/higher-order-functions.md index 27bba41ce2..79c14d7b03 100644 --- a/_zh-cn/tour/higher-order-functions.md +++ b/_zh-cn/tour/higher-order-functions.md @@ -1,6 +1,6 @@ --- layout: tour -title: Higher-order Functions +title: 高阶函数 discourse: false @@ -13,3 +13,98 @@ language: zh-cn next-page: nested-functions previous-page: mixin-class-composition --- + +高阶函数可以使用其他函数作为参数, 或者用函数作为返回结果, 因为函数在 Scala 语言中是第一类对象. 现在讨论专业术语会带来轻微的困惑, 并且我们使用"高阶函数"这个短语即表示方法又表示函数, 只要它们使用其他函数作为参数, 或者用函数作为返回结果. + +一个最常见的高阶函数的例子, 是 Scala 的集合类中的 `map` 方法. + +```tut +val salaries = Seq(20000, 70000, 40000) +val doubleSalary = (x: Int) => x * 2 +val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000) +``` + +`doubleSalary` 这个函数只有一个 `Int` 型参数 `x`, 并且返回 `x * 2`. 通常来说, 在箭头 `=>` 左边的元组是参数列表, 而在箭头右边的表达式的计算结果就是返回值. 在第3行, 函数 `doubleSalary` 会被应用到列表 `salaries` 中的每一个元素. + +为了简化代码, 我们可以将上面的函数变成一个匿名函数, 并且将它作为参数直接传递给 `map` 方法: +``` +val salaries = Seq(20000, 70000, 40000) +val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000) +``` +注意这里 `x` 没有声明成上面例子中的 `Int` 类型. 是因为编译器会根据函数 `map` 的可接受参数类型来推断传入参数的类型. 另一个更加符合使用习惯的方式可以写出以下等价的代码: + +```tut +val salaries = Seq(20000, 70000, 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 +} +``` +这里方法 `convertCtoF` 被传递给 `forecastInFahrenheit`. 这是允许的,因为编译器强制将方法 `convertCtoF` 转换成函数 `x => convertCtoF(x)` (注意: `x` 是一个随机生成的名字, 在当前的作用域内是唯一的). + +## 接受函数作为参数的高阶函数 +一个使用高阶函数的好处是减少多余的代码. 假设你需要一些方法用来根据不同情况来给某人涨薪水. 如果不使用高阶函数, 写出代码大概如下: + +```tut +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) +} +``` + +注意以上三个方法仅仅区别在相乘的因子上. 为了简化代码,你可以把重复的代码提取出来变成如下的高阶函数: + +```tut +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) +} +``` + +新的方法 `promotion`, 接受 salaries 和一个类型是 `Double => Double` 函数(即一个函数, 参数是 Double 型, 返回值也是 Double 型)作为参数, 返回应用了函数之后的结果. + +## 返回值是函数的高阶函数 + +在一些场景下你需要生成一个函数, 以下是一个用方法来生成函数的例子: + +```tut +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` 的返回值类型 `(String, String) => String`. 这意味着返回的匿名函数接受两个 String 型参数, 并且返回一个 String 型值. +在这个例子中, 返回的匿名函数是 `(endpoint: String, query: String) => s"https://www.example.com/$endpoint?$query"`. From e5af6de25af86f912f78e1776214ce98e7401110 Mon Sep 17 00:00:00 2001 From: realwunan Date: Mon, 15 Oct 2018 10:03:54 +0800 Subject: [PATCH 2/2] zh-cn for Scala Tour:singleton-objects.md --- _zh-cn/tour/singleton-objects.md | 99 +++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/_zh-cn/tour/singleton-objects.md b/_zh-cn/tour/singleton-objects.md index 70c32748d3..7d23be8b42 100644 --- a/_zh-cn/tour/singleton-objects.md +++ b/_zh-cn/tour/singleton-objects.md @@ -1,6 +1,6 @@ --- layout: tour -title: Singleton Objects +title: 单例对象 discourse: false @@ -13,3 +13,100 @@ language: zh-cn next-page: regular-expression-patterns previous-page: pattern-matching --- + +单例对象是一种特殊的类, 有且只有一个实例. 和惰性变量一样, 单例对象是延迟创建的, 当它被引用到的时候创建. + +作为一个顶级值, 单例对象只有一个实例. + +作为一种包装类, 或者局部值, 单例对象表现得和惰性变量一样. + +# 定义一个单例对象 +一个单例对象是就是一个值. 单例对象的定义方式很像类, 但是使用关键字 `object`: +```tut +object Box +``` + +下面例子中的单例对象包含一个方法: +``` +package logging + +object Logger { + def info(message: String): Unit = println(s"INFO: $message") +} +``` +方法 `info` 可以在程序中的任何地方被引用. 像这样创建功能性方法是单例对象的一种常见用法. + +下面让我们来看看如何在另外一个包中使用 `info` 方法: + +``` +import logging.Logger.info + +class Project(name: String, daysToComplete: Int) + +class Test { + val project1 = new Project("TPS Reports", 1) + val project2 = new Project("Website redesign", 5) + info("Created projects") // Prints "INFO: Created projects" +} +``` + +因为 import 语句 `import logging.Logger.info`, 方法 `info` 在此处是可见的. + +想要使用引用, 被引用的标识需要一个"固定路径", 一个单例对象就是一个固定路径. + +注意: 如果一个 `object` 没定义在顶层而是定义在另一个类或者单例对象中, 那么这个单例对象和其他类普通成员一样是 "路径相关的". 这意味着有两种行为, `class Milk` 和 `class OrangeJuice`, 一个类成员 `object NutritionInfo` "依赖" 于包装它的实例, 要么是牛奶要么是橙汁. `milk.NutritionInfo` 则完全不同于`oj.NutritionInfo`. + +## 伴生对象 + +当一个单例对象和某个类共享一个名称时, 这个单例对象称为 _伴生对象_. 同理, 这个类被称为是这个单例对象的伴生类. 类和它的伴生对象可以互相访问其私有成员. 使用伴生对象来定义那些在伴生类中不依赖于实例化对象而存在的成员变量或者方法. +``` +import scala.math._ + +case class Circle(radius: Double) { + import Circle._ + def area: Double = calculateArea(radius) +} + +object Circle { + private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0) +} + +val circle1 = new Circle(5.0) + +circle1.area +``` + +这里的 `class Circle` 有一个成员 `area` 是和具体的实例化对象相关的, 单例对象 `object Circle` 包含一个方法 `calculateArea` 则在每一个实例化对象中都是可见的. + +伴生对象也可以包含工厂方法: +```tut +class Email(val username: String, val domainName: String) + +object Email { + def fromString(emailString: String): Option[Email] = { + emailString.split('@') match { + case Array(a, b) => Some(new Email(a, b)) + case _ => None + } + } +} + +val scalaCenterEmail = Email.fromString("scala.center@epfl.ch") +scalaCenterEmail match { + case Some(email) => println( + s"""Registered an email + |Username: ${email.username} + |Domain name: ${email.domainName} + """) + case None => println("Error: could not parse email") +} +``` +伴生对象 `object Email` 包含有一个工厂方法 `fromString` 用来根据一个 String 创建 `Email` 实例. 在这里我们返回的是 `Option[Email]` 以防有语法分析错误. + +注意: 类和它的伴生对象必须定义在同一个源文件里. 如果需要在 REPL 里定义类和其伴生对象, 需要将它们定义在同一行或者进入 `:paste` 模式. + +## Java 程序员的注意事项 ## + +在 Java 中 `static` 成员对应于 Scala 中的伴生对象的普通成员. + +在 Java 代码中调用伴生对象时, 伴生对象的成员会被定义成伴生类中的 `static` 成员. 这称为_静态转发_. 这种行为发生在当你自己没有定义一个伴生类时.