diff --git a/book/contents/advance/functional-programing/closure.md b/book/contents/advance/functional-programing/closure.md index 618a04c4..1714a58c 100644 --- a/book/contents/advance/functional-programing/closure.md +++ b/book/contents/advance/functional-programing/closure.md @@ -1,6 +1,6 @@ # 闭包closure -闭包这个词语由来已久,自上世纪60年代就由 `Scheme` 语言引进之后,被广泛用于函数式编程语言中,进入21世纪后,各种现代化的编程语言也都不约而同地把闭包作为核心特性纳入到语言设计中来。那么到底何为闭包? +闭包这个词语由来已久,自上世纪 60 年代就由 `Scheme` 语言引进之后,被广泛用于函数式编程语言中,进入 21 世纪后,各种现代化的编程语言也都不约而同地把闭包作为核心特性纳入到语言设计中来。那么到底何为闭包? 闭包是**一种匿名函数,它可以赋值给变量也可以作为参数传递给其它函数,不同于函数的是,它允许捕获调用者作用域中的值**,例如: ```rust @@ -12,7 +12,7 @@ fn main() { } ``` -上面的代码展示了非常简单的闭包 `sum`,它拥有一个入参 `y`,同时捕获了作用域中的 `x` 的值,因此调用 `sum(2)` 意味着将 2(参数 `y`) 跟 1(`x`)进行相加,最终返回它们的和:`3`。 +上面的代码展示了非常简单的闭包 `sum`,它拥有一个入参 `y`,同时捕获了作用域中的 `x` 的值,因此调用 `sum(2)` 意味着将 2(参数 `y`)跟 1(`x`)进行相加,最终返回它们的和:`3`。 可以看到 `sum` 非常符合闭包的定义:可以赋值给变量,允许捕获调用者作用域中的值。 @@ -41,15 +41,13 @@ fn workout(intensity: u32, random_number: u32) { "旁边有妹子在看,俯卧撑太low,再来 {} 组卧推!", muuuuu(intensity) ); + } else if random_number == 3 { + println!("昨天练过度了,今天还是休息下吧!"); } else { - if random_number == 3 { - println!("昨天练过度了,今天还是休息下吧!"); - } else { - println!( - "昨天练过度了,今天干干有氧,跑步 {} 分钟!", - muuuuu(intensity) - ); - } + println!( + "昨天练过度了,今天干干有氧,跑步 {} 分钟!", + muuuuu(intensity) + ); } } @@ -64,11 +62,21 @@ fn main() { } ``` -可以看到,在健身时我们根据想要的强度来调整具体的动作,然后调用 `muuuuu` 函数来开始健身。这个程序本身很简单,没啥好说的,但是假如未来不用 `muuuu` 函数了,是不是得把所有 `muuuu` 都替换成,比如说 `woooo` ?如果 `muuuu` 出现了几十次,那意味着我们要修改几十处地方。 +可以看到,在健身时我们根据想要的强度来调整具体的动作,然后调用 `muuuuu` 函数来开始健身。这个程序本身很简单,没啥好说的,但是假如未来不用 `muuuuu` 函数了,是不是得把所有 `muuuuu` 都替换成,比如说 `woooo` ?如果 `muuuuu` 出现了几十次,那意味着我们要修改几十处地方。 #### 函数变量实现 一个可行的办法是,把函数赋值给一个变量,然后通过变量调用: ```rust +use std::thread; +use std::time::Duration; + +// 开始健身,好累,我得发出声音:muuuu... +fn muuuuu(intensity: u32) -> u32 { + println!("muuuu....."); + thread::sleep(Duration::from_secs(2)); + intensity +} + fn workout(intensity: u32, random_number: u32) { let action = muuuuu; if intensity < 25 { @@ -80,17 +88,25 @@ fn workout(intensity: u32, random_number: u32) { "旁边有妹子在看,俯卧撑太low, 再来 {} 组卧推!", action(intensity) ); + } else if random_number == 3 { + println!("昨天练过度了,今天还是休息下吧!"); } else { - if random_number == 3 { - println!("昨天练过度了,今天还是休息下吧!"); - } else { - println!( - "昨天练过度了,今天干干有氧, 跑步 {} 分钟!", - action(intensity) - ); - } + println!( + "昨天练过度了,今天干干有氧, 跑步 {} 分钟!", + action(intensity) + ); } } + +fn main() { + // 强度 + let intensity = 10; + // 随机值用来决定某个选择 + let random_number = 7; + + // 开始健身 + workout(intensity, random_number); +} ``` @@ -104,6 +120,9 @@ fn workout(intensity: u32, random_number: u32) { 上面提到 `intensity` 要是变化怎么办,简单,使用闭包来捕获它,这是我们的拿手好戏: ```rust +use std::thread; +use std::time::Duration; + fn workout(intensity: u32, random_number: u32) { let action = || { println!("muuuu....."); @@ -120,15 +139,13 @@ fn workout(intensity: u32, random_number: u32) { "旁边有妹子在看,俯卧撑太low,再来 {} 组卧推!", action() ); + } else if random_number == 3 { + println!("昨天练过度了,今天还是休息下吧!"); } else { - if random_number == 3 { - println!("昨天练过度了,今天还是休息下吧!"); - } else { - println!( - "昨天练过度了,今天干干有氧,跑步 {} 分钟!", - action() - ); - } + println!( + "昨天练过度了,今天干干有氧,跑步 {} 分钟!", + action() + ); } } @@ -167,7 +184,7 @@ Rust 闭包在形式上借鉴了 `Smalltalk` 和 `Ruby` 语言,与函数最大 ## 闭包的类型推导 Rust 是静态语言,因此所有的变量都具有类型,但是得益于编译器的强大类型推导能力,在很多时候我们并不需要显式地去声明类型,但是显然函数并不在此列,必须手动为函数的所有参数和返回值指定类型,原因在于函数往往会作为 API 提供给你的用户,因此你的用户必须在使用时知道传入参数的类型和返回值类型。 -与函数相反,闭包并不会作为API对外提供,因此它可以享受编译器的类型推导能力,无需标注参数和返回值的类型。 +与函数相反,闭包并不会作为 API 对外提供,因此它可以享受编译器的类型推导能力,无需标注参数和返回值的类型。 为了增加代码可读性,有时候我们会显式地给类型进行标注,出于同样的目的,也可以给闭包标注类型: ```rust @@ -176,10 +193,10 @@ let sum = |x: i32, y: i32| -> i32 { } ``` -与之相比,不标注类型的闭包声明会更简洁些:`let sum = |x, y| x + y`,需要注意的是,针对 `sum` 闭包,如果你只进行了声明,但是没有使用,编译器会提示你为 `x,y` 添加类型标注,因为它缺乏必要的上下文: +与之相比,不标注类型的闭包声明会更简洁些:`let sum = |x, y| x + y`,需要注意的是,针对 `sum` 闭包,如果你只进行了声明,但是没有使用,编译器会提示你为 `x, y` 添加类型标注,因为它缺乏必要的上下文: ```rust let sum = |x, y| x + y; -let v = sum(1,2); +let v = sum(1, 2); ``` 这里我们使用了 `sum`,同时把 `1` 传给了 `x`,`2` 传给了 `y`,因此编译器才可以推导出 `x,y` 的类型为 `i32`。 @@ -274,7 +291,7 @@ where ## 捕获作用域中的值 -在之前代码中,我们一直在用闭包的匿名函数特性(赋值给变量),然而闭包还拥有一项函数所不具备的特性:捕获作用域中的值。 +在之前代码中,我们一直在用闭包的匿名函数特性(赋值给变量),然而闭包还拥有一项函数所不具备的特性:捕获作用域中的值。 ```rust fn main() { let x = 4; diff --git a/book/contents/advance/functional-programing/intro.md b/book/contents/advance/functional-programing/intro.md index 1e23912b..07266ed9 100644 --- a/book/contents/advance/functional-programing/intro.md +++ b/book/contents/advance/functional-programing/intro.md @@ -1,6 +1,7 @@ # 函数式编程 罗马不是一天建成的,编程语言亦是如此,每一门编程语言在借鉴前辈的同时,也会提出自己独有的特性,Rust 即是如此。当站在巨人肩膀上时,一个人所能看到的就更高更远,恰好,我们看到了函数式语言的优秀特性,例如: + - 使用函数作为参数进行传递 - 使用函数作为函数返回值 - 将函数赋值给变量 @@ -8,6 +9,7 @@ 见猎心喜,我们忍不住就借鉴了过来,于是你能看到本章的内容,天下语言一大。。。跑题了。 关于函数式编程到底是什么的争论由来已久,本章节并不会踏足这个泥潭,因此我们在这里主要关注的是函数式特性: + - 闭包closure - 迭代器iterator - 模式匹配 diff --git a/book/contents/advance/functional-programing/iterator.md b/book/contents/advance/functional-programing/iterator.md index 2eabcbb8..d84d53f3 100644 --- a/book/contents/advance/functional-programing/iterator.md +++ b/book/contents/advance/functional-programing/iterator.md @@ -6,7 +6,7 @@ ## For 循环与迭代器 从用途来看,迭代器跟 `for` 循环颇为相似,都是去遍历一个集合,但是实际上它们存在不小的差别,其中最主要的差别就是:**是否通过索引来访问集合**。 -例如以下的JS代码就是一个循环: +例如以下的 JS 代码就是一个循环: ```javascript let arr = [1, 2, 3]; for (let i = 0; i < arr.length; i++) { @@ -26,7 +26,7 @@ for v in arr { 那又有同学要发问了,在 Rust 中数组是迭代器吗?因为在之前的代码中直接对数组 `arr` 进行了迭代,答案是 `No`。那既然数组不是迭代器,为啥咱可以对它的元素进行迭代呢? -简而言之就是数组实现了 `IntoIterator` 特征,Rust 通过 `for` 语法糖,自动把实现了该特征的数组类型转换为迭代器(你也可以为自己的集合类型实现此特征),最终让我们可以直接对一个数组进行迭代,类似的还有: +简而言之就是数组实现了 `IntoIterator` 特征,Rust 通过 `for` 语法糖,自动把实现了该特征的数组类型转换为迭代器(你也可以为自己的集合类型实现此特征),最终让我们可以直接对一个数组进行迭代,类似的还有: ```rust for i in 1..10 { println!("{}", i); @@ -45,7 +45,7 @@ for v in arr.into_iter() { 迭代器是函数语言的核心特性,它赋予了 Rust 远超于循环的强大表达能力,我们将在本章中一一为大家进行展现。 ## 惰性初始化 -在Rust中,迭代器是惰性的,意味着如果你不使用它,那么它将不会发生任何事: +在 Rust 中,迭代器是惰性的,意味着如果你不使用它,那么它将不会发生任何事: ```rust let v1 = vec![1, 2, 3]; @@ -95,7 +95,7 @@ fn main() { - `next` 方法返回的是 `Option` 类型,当有值时返回 `Some(i32)`,无值时返回 `None` - 遍历是按照迭代器中元素的排列顺序依次进行的,因此我们严格按照数组中元素的顺序取出了 `Some(1)`,`Some(2)`,`Some(3)` -- 手动迭代必须将迭代器声明为 `mut` 可变,因为调用 `next` 会改变迭代器其中的状态数据(当前遍历的位置等),而 `for` 循环去迭代则无需标注 `mut`,因为它会帮我们自动完成 +- 手动迭代必须将迭代器声明为 `mut` 可变,因为调用 `next` 会改变迭代器其中的状态数据(当前遍历的位置等),而 `for` 循环去迭代则无需标注 `mut`,因为它会帮我们自动完成 总之,`next` 方法对**迭代器的遍历是消耗性的**,每次消耗它一个元素,最终迭代器中将没有任何元素,只能返回 `None`。 @@ -254,7 +254,7 @@ warning: unused `Map` that must be used | ^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_must_use)]` on by default - = note: iterators are lazy and do nothing unless consumed // 迭代器map是惰性的,这里不产生任何效果 + = note: iterators are lazy and do nothing unless consumed // 迭代器 map 是惰性的,这里不产生任何效果 ``` 如上述中文注释所说,这里的 `map` 方法是一个迭代者适配器,它是惰性的,不产生任何行为,因此我们还需要一个消费者适配器进行收尾: @@ -305,7 +305,7 @@ fn shoes_in_size(shoes: Vec, shoe_size: u32) -> Vec { `filter` 是迭代器适配器,用于对迭代器中的每个值进行过滤。 它使用闭包作为参数,该闭包的参数 `s` 是来自迭代器中的值,然后使用 `s` 跟外部环境中的 `shoe_size` 进行比较,若相等,则在迭代器中保留 `s` 值,若不相等,则从迭代器中剔除 `s` 值,最终通过 `collect` 收集为 `Vec` 类型。 ## 实现 Iterator 特征 -之前的内容我们一直基于数组来创建迭代器,实际上,不仅仅是数组,基于其它集合类型一样可以创建迭代器,例如 `HashMap`。 你也可以创建自己的迭代器 - 只要为自定义类型实现 `Iterator` 特征即可。 +之前的内容我们一直基于数组来创建迭代器,实际上,不仅仅是数组,基于其它集合类型一样可以创建迭代器,例如 `HashMap`。 你也可以创建自己的迭代器 —— 只要为自定义类型实现 `Iterator` 特征即可。 首先,创建一个计数器: ```rust @@ -464,14 +464,12 @@ test bench::bench_iter ... bench: 983,858 ns/iter (+/- 44,673) 迭代器是 Rust 的 **零成本抽象**(zero-cost abstractions)之一,意味着抽象并不会引入运行时开销,这与 `Bjarne Stroustrup`(C++ 的设计和实现者)在 `Foundations of C++(2012)` 中所定义的 **零开销**(zero-overhead)如出一辙: -``` -In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. -And further: What you do use, you couldn’t hand code any better. - -一般来说,C++的实现遵循零开销原则:没有使用时,你不必为其买单。 -更进一步说,需要使用时,你也无法写出更优的代码了。 -(翻译一下:用就完事了) -``` +> In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. +> And further: What you do use, you couldn’t hand code any better. +> +> 一般来说,C++的实现遵循零开销原则:没有使用时,你不必为其买单。 +> 更进一步说,需要使用时,你也无法写出更优的代码了。 +> (翻译一下:用就完事了) 总之,迭代器是 Rust 受函数式语言启发而提供的高级语言特性,可以写出更加简洁、逻辑清晰的代码。编译器还可以通过循环展开(Unrolling)、向量化、消除边界检查等优化手段,使得迭代器和 `for` 循环都有极为高效的执行效率。