(译)Functional Programming – a Comparison

书名 Hands-On Functional Programming in Rust

原书可以在 www.packtpub.com 购买

欢迎评论区提翻译建议

Functional programming 函数式编程(简称 FP)是仅次于面向对象(OOP)的第二大流行的编程范式。多年来,这两种不同的编程范式衍生出不同的语言,所以不要混淆两者。而多范式语言想要同时支持这两种范式。Rust 就是这样一门多范式语言。

通常情况下,函数式编程强调使用可组合和最大可重用函数来定义程序行为。使用这些技术,我们将展示函数式编程如何将巧妙的解决方案应用于许多常见难题中。本章将概述本书中将要介绍的大多数概念。其余章节将专门帮助你掌握每种技术。

我们希望你能学会如下技能:

  • 能使用函数式风格来减少代码量以及降低代码的复杂性
  • 可以利用安全的抽象来写出具有健壮安全的代码
  • 可以使用函数式原理设计工程复杂的项目

环境要求

要运行我们提供的例子,需要最新的 Rust,可以在这里找到:

www.rust-lang.org/en-US/insta…

本章的代码也可在 GitHub 上找到:

github.com/PacktPublis…

每章的 README.md 文件包含了特定的安装和构建说明。

减少代码量和复杂度

函数式编程可以大大减少完成任务所需的代码量以及降低复杂度。特别是在 Rust 中,正确使用函数式原理可以简化通常情况下很复杂的设计,同时提高开发效率。

让通用类型更通用

让通用类型更通用的思想源于函数式编程的参数化数据结构和功能实践。在 Rust 和其他一些语言中,这被称为泛型(generics)。类型和函数都可以被参数化。可以在通用类型上增加单个或多个约束来达到 trait 和生命周期的要求。

假如没有泛型,结构体( struct )的定义可能会更繁琐。这里我们定义了三个各包含相同类型的 Point 结构体。但是,这些结构体分别使用不同的数值类型,所以在 intro_generics.rs 中将它们作为三个单独的 PointN 来定义:

struct PointU32
{
    x: u32,
    y: u32
}

struct PointF32
{
    x: f32,
    y: f32
}

struct PointI32
{
    x: i32,
    y: i32
}
复制代码

其实,我们可以使用泛型来精简重复的代码同时让代码更健壮。加了泛型的代码更容易适应新的需求,因为许多行为(或需求)可以被参数化。假如需要改需求,也只要改一行而不是改上百行。

这段代码定义了一个参数化的 Point 结构体。现在,在 intro_generics.rs 只要一个定义就可以匹配所有可能的数值类型:

struct Point<T>
{
    x: T,
    y: T
}
复制代码

要是没有泛型的话函数也存在同样的问题。

这里有个简单的函数用来计算某数的平方。可惜为了匹配尽可能多的数值类型,我们需要在 intro_generics.rs 定义三个不同的函数:

fn foo_u32(x: u32) -> u32
{
    x*x
}

fn foo_f32(x: f32) -> f32
{
    x*x
}

fn foo_i32(x: i32) -> i32
{
    x*x
}
复制代码

类似的函数可能需要 trait bounds (指定一个或多个特定 trait 约束)让函数可以使用该类型的任意行为。

这里是一个使用参数化类型(译注:原文 “parameterized type”,在 Haskell 等语言中很常见)重新定义的 foo 函数。只要一个函数就可以定义包含所有数值类型的运算。不过必须设定显示的范围包括基本的运算,譬如乘法及其他具备 copy 行为数据的运算, intro_generics.rs

fn foo<T>(x: T) -> T
where T: std::ops::Mul<Output=T> + Copy
{
    x*x
}
复制代码

甚至函数也可以作为参数。我们称之为高阶函数。 这是一个简单的函数,它接收一个函数和参数,然后使用该函数和参数,同时返回结果。注意 trait bound Fn ,这表示需要提供的函数是一个闭包(closure)。为了使某对象可以被调用,它必须实现 fnFnFnMut ,或者 FnOnce traitintro_generics.rs

fn bar<F, T>(f: F, x: T) -> T
where F: Fn(T) -> T
{
    f(x)
}
复制代码

函数作为值

函数式编程最明显的特点就是函数。具体讲,将函数作为值是函数式编程的基础。由于涉及很多细节,我们会先介绍 “闭包” 来为之后的内容作铺垫。“闭包” 是实现了 fnFnFnMut ,或者 FnOnce trait 的对象。

简单的闭包可以使用内置的闭包语法来定义。因为通常情况下使用此语法可以自动实现 fnFnFnMut ,或者 FnOnce trait 。这个语法非常适合数据快捷操作。

现在创建一个 0 到 10 范围的迭代器,并对每个值做平方映射。这个平方映射操作是给迭代器的 map 函数传一个内联闭包。表达式如下, intro_functions.rs

(0..10).map(|x| x*x);
复制代码

如果使用 block 语法,则闭包内可以写多条语句的复杂主体。

以下是一个 0 到 10 范围的迭代器,映射一个复杂的方程。闭包给迭代器 map 提供一个函数定义和变量绑定, intro_functions.rs

(0..10).map(|x| {
    fn f(y: u32) -> u32 {
        y*y
    }
    let z = f(x+1) * f(x+2);
    z*z
})
复制代码

可以定义接收闭包作为参数的函数或方法。要将闭包作为可供调用函数,就必须指定 FnFnMutFnOnce trait bound

这是一个接收函数 g 和参数 x 的高阶函数(HoF)。它限定 gx 处理 u32 类型,并定义跟 g 相关的运算。高阶函数 f 紧跟着简单的内联闭包用来调用, intro_function.rs

fn f<T>(g: T, x: u32) -> u32
where T: Fn(u32) -> u32
{
    g(x+1) * g(x+2)
}
fn main()
{
    f(|x|{ x*x }, 2);
}
复制代码

标准库内多个区域,特别是迭代器(iterators)大量使用函数作为参数。

这里有一个从 0 到 10 范围的迭代器链式调用一堆迭代组合器。 map 函数从原始值返回新的值。 inspect 遍历每个值,虽然不会改变它,但是该函数会产生副作用。 filter 会忽略所有不满足谓词(译注:可以理解为逻辑条件)的值。 filter_map 使用单个函数来做过滤和映射。 fold (译注:学过 Java8 或者 JavaScript 之类语言的同学可以类比 reduce 来理解,还有一个 rfold 右折叠函数,在 Haskell 中对应 foldl foldr ) 会把所有的结果从初始值从左到右减少到单个值。 intro_functions.rs

(0..10).map(|x| x*x)
        .inspect(|x|{ println!("value {}", *x) })
        .filter(|x| *x<3)
        .filter_map(|x| Some(x))
        .fold(0, |x,y| x+y);
复制代码
我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章