ExamplesBy LevelBy TopicLearning Paths
259 Intermediate

259: Flattening with flat_map()

Functional Programming

Tutorial

The Problem

Many real-world transformations are one-to-many: splitting a sentence into words, expanding a range for each element, or parsing optional values from strings. A plain map() yields Iterator<Item = Iterator<...>> — a nested structure that requires an additional flatten() call to linearize. The flat_map() combinator (also known as bind or >>= in monadic contexts) fuses these two operations, mapping each element to an iterable and immediately flattening the result into a single stream.

🎯 Learning Outcomes

  • • Understand flat_map(f) as equivalent to map(f).flatten() — the monadic bind for iterators
  • • Use flat_map() to expand one element into multiple values
  • • Filter while transforming by returning empty iterators for rejected elements
  • • Recognize flat_map() as the same as Haskell's concatMap and OCaml's List.concat_map
  • Code Example

    #![allow(clippy::all)]
    //! 259. Flattening with flat_map()
    //!
    //! `flat_map(f)` = `map(f).flatten()` — the iterator monad's bind operation.
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_flat_map_expand() {
            let result: Vec<i32> = [1i32, 2, 3].iter().flat_map(|&n| 0..n).collect();
            assert_eq!(result, vec![0, 0, 1, 0, 1, 2]);
        }
    
        #[test]
        fn test_flat_map_filter_parse() {
            let strings = ["1", "x", "2", "y", "3"];
            let result: Vec<i32> = strings.iter().flat_map(|s| s.parse::<i32>()).collect();
            assert_eq!(result, vec![1, 2, 3]);
        }
    
        #[test]
        fn test_flat_map_words() {
            let sentences = ["hello world", "foo bar"];
            let words: Vec<&str> = sentences
                .iter()
                .flat_map(|s| s.split_whitespace())
                .collect();
            assert_eq!(words.len(), 4);
        }
    }

    Key Differences

  • Name: Rust calls it flat_map; Haskell calls it concatMap; OCaml uses List.concat_map; all express the same monadic bind operation.
  • Return type flexibility: Rust's flat_map accepts any IntoIterator, including Option and Result which iterate to 0 or 1 elements — enabling inline filtering.
  • Laziness: Rust processes lazily; OCaml's List.concat_map builds a new list eagerly.
  • Error handling integration: Returning Result::ok() or Option from flat_map naturally filters failures without a separate filter() call.
  • OCaml Approach

    OCaml provides List.concat_map f xs (or List.map f xs |> List.concat for older versions), which is exactly flat_map. The monadic bind >>= for the list monad is defined as fun xs f -> List.concat_map f xs:

    let words = List.concat_map String.split_on_char [' '] ["hello world"; "foo bar"]
    (* ["hello"; "world"; "foo"; "bar"] *)
    

    Full Source

    #![allow(clippy::all)]
    //! 259. Flattening with flat_map()
    //!
    //! `flat_map(f)` = `map(f).flatten()` — the iterator monad's bind operation.
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_flat_map_expand() {
            let result: Vec<i32> = [1i32, 2, 3].iter().flat_map(|&n| 0..n).collect();
            assert_eq!(result, vec![0, 0, 1, 0, 1, 2]);
        }
    
        #[test]
        fn test_flat_map_filter_parse() {
            let strings = ["1", "x", "2", "y", "3"];
            let result: Vec<i32> = strings.iter().flat_map(|s| s.parse::<i32>()).collect();
            assert_eq!(result, vec![1, 2, 3]);
        }
    
        #[test]
        fn test_flat_map_words() {
            let sentences = ["hello world", "foo bar"];
            let words: Vec<&str> = sentences
                .iter()
                .flat_map(|s| s.split_whitespace())
                .collect();
            assert_eq!(words.len(), 4);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_flat_map_expand() {
            let result: Vec<i32> = [1i32, 2, 3].iter().flat_map(|&n| 0..n).collect();
            assert_eq!(result, vec![0, 0, 1, 0, 1, 2]);
        }
    
        #[test]
        fn test_flat_map_filter_parse() {
            let strings = ["1", "x", "2", "y", "3"];
            let result: Vec<i32> = strings.iter().flat_map(|s| s.parse::<i32>()).collect();
            assert_eq!(result, vec![1, 2, 3]);
        }
    
        #[test]
        fn test_flat_map_words() {
            let sentences = ["hello world", "foo bar"];
            let words: Vec<&str> = sentences
                .iter()
                .flat_map(|s| s.split_whitespace())
                .collect();
            assert_eq!(words.len(), 4);
        }
    }

    Exercises

  • Split a Vec<String> of sentences into individual words using flat_map() and split_whitespace().
  • Use flat_map() with Option to look up each key in a map and collect only the found values into a Vec.
  • Implement flat_map from scratch using only map() and flatten() and verify it produces identical results.
  • Open Source Repos