《Rust权威指南》 第13章 函数式语言特征:迭代器与闭包

想要了解更多函数式语言的特点,可以去看sicp

闭包:能够捕获环境变量的匿名函数

闭包是一种可以存入变量或者作为参数传递给其它函数的匿名函数(在别的语言里可能是ambda表达式)
创建闭包:查看下面的例子,基本上是|参数| {函数体}

let f = |x:u32| -> u32 {x+1}; //创建闭包,不省略参数类型和返回值类型
let f = |x|            {x+1}; //创建闭包,自动推断类型
let f = |x|             x+1 ; //只含一个表达式的闭包可以省略大括号

fn f    (x:u32) -> u32 {x+1}  //函数定义

闭包的类型推断和类型标注

编译器可以根据上下文推断出闭包中参数的类型和返回的类型,但是注意:
闭包中的类型最终会被推定为唯一的具体类型,也就是说下面这样是不行的

let f = |x| x;
let s = f("a");
let a = f(1);

使用泛型参数和Fn trait存储闭包:以惰性求值为例

惰性化求值是一种思想,其将已经得到的值缓存起来,这样再请求时可以直接去缓存里寻找;只有新值需要求
可以这么实现:创建一个同时存放闭包和闭包返回值的结构体

  • 需要获得值时运行背包,并将结果缓存到别的字段
  • 请求旧值时,直接到缓存字段查找

为了将闭包存储在结构体中,我们使用一个实现了Fn trait的泛型来指示闭包的类型
Fn trait需要知道闭包的参数和返回类型,像是Fn(u32)->u32

给出一个例子:

struct Cacher<T>
        where T: Fn(u32)->u32
{
    cal:T,
    value:Option<u32>,
}

impl<T> Cacher<T> where T:Fn(u32)->u32 {
    fn new(cal:T) -> Cacher<T> {
        Cacher { cal, value: None, }
    }

    fn value(&mut self,arg:u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.cal)(arg);
                self.value = v;
                v
            },
        }
    }
}

改进

由于上面只会存储第一次调用的结果,让我们很难在不同的上下文中复用

  • 解决方法是在结构体中存储一个map,以传入的arg为关键字,以返回值为键(就是将函数做成查找表)
    第二个问题是拓展性差
  • 解决方法是引入更多的泛型参数

可能看起来一个接受一个参数返回一个结果的Cacher是下面这样

struct Cacher<T,U,V>
        where U:Copy+Eq+Hash,V:Copy,T: Fn(U)->V
{
    cal:T,
    value:HashMap<U,V>,
}

impl<T,U,V> Cacher<T,U,V> where U:Copy+Eq+Hash,V:Copy,T: Fn(U)->V {
    fn new(cal:T) -> Cacher<T,U,V> {
        Cacher { cal, value: HashMap::new(), }
    }

    fn value(&mut self,arg:U) -> V {
        let v = (self.value).entry(arg).or_insert(
            (self.cal)(arg)
        );
        *v
    }
}

使用闭包捕获上下文环境

闭包可以捕获自己所在的环境并访问自己被定义时作用域中的变量(还记得c++中,捕获变量列表位于lambda表达式一开始的[]内)
当闭包从环境中捕获值时,会使用额外的空间来储存这些值以在闭包体中使用
闭包可以通过三种方式从环境中捕获值,这三种方式被分别编码在如下所示的3种Fn系列的trait中

  • FnOnce,意味着闭包可以从环境中获得变量的所有权
  • FnMut,意味着闭包可以获得环境中变量的可变引用
  • Fn 可以从环境中不可变的借用值

闭包的trait可以被自动推导出

  • 所有闭包都实现了FnOnce
    • 不需要move被捕获变量的闭包实现了FnMut
      • 不需要修改被捕获变量的实现了Fn

如果希望闭包强制捕获所有权,在参数列表前加move关键字(该特性在多线程中尤其有用)

let x = vec![1,2,3];
let f = move |z| z==x;

在上面的例子中,x被强制移动到闭包内,即在闭包定义后面就不许使用x这个名称了
倘若上面的例子去掉move关键字,则该闭包只会获得环境变量的不可变引用

使用迭代器处理元素序列

迭代器模式使得你可以依次为序列中的每一个元素执行某些任务

迭代器是惰性的创建迭代器后,除非主动调用方法消耗并使用迭代器,否则不会产生任何实际效果

Iterator trait 和 next 方法

所有迭代器都实现了Iterator trait,该trait定义如下所示

pub trait Iterator {
	type item;
	fn next(&mut self) -> Option<Self::item>;
	//以及一系列的默认方法,大部分是基于next方法的
}

Item是关联类型,这里先不做深入讨论
该trait最重要的点在于实现next这个方法,在每次调用时返回一个包裹在Some中的迭代器元素,并在迭代结束时返回None

创建迭代器

对于大部分的容器类型

  • .iter()生成不可变引用迭代器
  • .into_iter()取得所有权并返回元素本身的迭代器
  • .iter_mut()生成可变引用迭代器

自定义的迭代器类型

分为三步:创建一个类型,为该类型实现Iterator trai (指定item类型,定义next方法)

比如我们做一个倒计时迭代器

struct Counter {
	count:u32
}

impl Counter {
	fn new() -> Counter {
		Counter {count:11}
	}
}

impl Iterator for Counter {
	type Item = u32;

	fn next(&mut self) -> Option<Self::Item> {
		self.count -= 1;
		if self.count > 0 {
			Some(self.count)
		} else {
			None
		}
	}
}

消耗迭代器的方法

所有调用next的方法称为消耗适配器,Iterator trait提供了很多这类的方法

let total = v.iter().sum();

生成其它迭代器的方法

将一个迭代器转化成另一个迭代器方法称为迭代器适配器
因为所有迭代器都是惰性的,你总是需要一个消耗适配器才能得到效果

  • map :接受一个闭包作为参数,闭包的参数是迭代器返回的类型,遍历迭代器,将其元素经过闭包处理,并输出成迭代器
//将所有数字加1
let v2:Vec<_> = v1.iter().map(|x| x+1).collect();
  • filter:接受一个返回布尔值的闭包作为参数,闭包的参数是迭代器返回的类型,遍历迭代器,保留闭包返回true的元素,输出成迭代器
//取出所有偶数
let v2:Vec<_> = v1.iter().filter(|x| x%2==0).collect();

使用迭代器重构I/O项目

主要是两点

  • 在Config::new中直接接受std:env::args作为参数
pub fn new(mut args:std::env::Args) -> Result<Config,&'static str> {
        args.next();

        let query = match args.next() {
            Some(arg) => arg,
            None => return Err("lack of query string."),
        };
        let filename = match args.next() {
            Some(arg) => arg,
            None => return Err("lack of file name."),
        };
        
        let case_sensitive = env::var("CASE_SENSITIVE").is_err();

        Ok(Config { query, filename, case_sensitive})
}
  • 使用链式的风格,替代循环
pub fn search_case_insensitive<'a>(query:&str,contents:&'a str) -> Vec<&'a str> {
    let query = query.to_lowercase();
    contents.lines()
            .filter(|line| (line.to_lowercase()).contains(&query))
            .collect()
}

重构后的项目在nanogrep

迭代器VS循环

迭代器是Rust中的零开销抽象,这意味着使用迭代器这个抽象时不会引入额外的运行时开销

大部分的时候,使用迭代器甚至要比使用循环要快速,这依赖于编译器对迭代器的优化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值