-
R, at its heart, is a functional language - Hadley Wickham, Advanced R
Emphasizes purity: Focuses on side-effect free, “true” functions.
Avoid mutability: Promote “assignment free” programming.
Encourages declarative style: Evaluating expressions over executing statements.
Treats functions as first-class citizens: Functions are values too.
Rust has extensive support for programming in a functional style:
map, filter, fold)Option, Result promote predictable, safe error handling.This chapter focuses on the two we have yet to cover: Closures and Iterators.
Closures : Function values that capture variables from the environment
Iterators : Abstractions for lazy processing of sequences
Core to functional programming:
map, filter)Example in R:
example_closure <- function(x) {
y <- 10
function() {
x + y
}
}
closure_instance <- example_closure(5)
closure_instance() # Returns 15||instead of ()fn outer_function() {
let outer_variable = 10;
// Define an inner function
fn inner_function() {
// Attempt to access outer_variable
//println!("{}", outer_variable); // Error: can't capture dynamic environment in a fn item
}
// Captures `outer_variable` from the environment
let inner_closure = || {
println!("{}", outer_variable);
};
inner_function();
inner_closure();
}
fn main(){
outer_function();
}#[derive(Debug, PartialEq, Copy, Clone)]
enum ShirtColor {
Red,
Blue,
}
struct Inventory {
shirts: Vec<ShirtColor>,
}
impl Inventory {
fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
user_preference.unwrap_or_else(|| self.most_stocked())
}
fn most_stocked(&self) -> ShirtColor {
let mut num_red = 0;
let mut num_blue = 0;
for color in &self.shirts {
match color {
ShirtColor::Red => num_red += 1,
ShirtColor::Blue => num_blue += 1,
}
}
if num_red > num_blue {
ShirtColor::Red
} else {
ShirtColor::Blue
}
}
}unwrap_or_else.|| self.most_stocked() if user_preference is None variant.self (immutable reference to Inventory)Closure can capture values in the same three ways that functions can take parameters
Immutable Reference
Mutable reference
Moving ownership
Can also explicitly move ownership:
Rust will use the highest on the list possible: Immutable reference, then mutable if needed, and then move it if needed or requested.
F is the generic type, with trait bound FnOnce() -> TF can be any closure, since we only call it once.Standard library function on slices that takes a function that produces the sort key K.
Ord is a trait for ordering, requires cmp function, implemented by orderable types (e.g. all numbers)
Takes a FnMut instead of FnOnce because it must be called multiple times to do the sort.
sort_by_key usage exampleFnMut so OK.value out.FnOnce, not FnMut.FnMut (in addition to FnOnce)iter() on a collection by itself does nothingsum(), collect(), or a for loop consume the iteratorlet v1 = vec![1, 2, 3];
// Creating an iterator (does nothing yet)
let v1_iter = v1.iter();
// Consuming the iterator in a for loop
for val in v1_iter {
println!("Got: {val}");
}N.B. The for syntax will call iter for you, so for val in v1 will also work.
next.Item is an associated type. More on this in Chapter 19.nextnext takes mutable ref to self.iter_mut gives mutable referencesinto_iter gives owned values.next on an iterator.sum and collectfilter and map.x in the filter since iter gives &&i32v2 is not optional, collect can produce different types of collections.Rust
Haskell
clone calls.Config::Build didn’t own args.Config::Build take ownership of an iterator to the args.env::args() returns and iterator.buildConfig::build signatureargs is that it implements an iterator that returns String items.impl Trait syntax was covered in Chapter 10 and is syntactic sugar for a type variable with a trait bound.Config::build bodyimpl Config {
pub fn build(
mut args: impl Iterator<Item = String>,
) -> Result<Config, &'static str> {
args.next();
let query = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a query string"),
};
let file_path = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a file path"),
};
let ignore_case = env::var("IGNORE_CASE").is_ok();
Ok(Config {
query,
file_path,
ignore_case,
})
}
}search function to a functional style.results in the original code).filter:
results)containslet numbers = vec![1, 2, 3, 4, 5];
let mut result = Vec::new();
for &num in &numbers {
if num % 2 == 0 {
result.push(num * 2);
}
}
println!("Result: {:?}", result);Iterators often compile to machine code that is as efficient as hand-written loops. This is referred to as a zero-cost abstraction
This enables concise, declarative code without sacrificing performance.
Functional programming emphasizes evaluating expressions over executing statements.
Rust provides robust support for functional programming:
map, filter) for transforming data.Enables concise, safe code without sacrificing performance.